FIR checker: introduce PARAMETER_* positioning strategies
and use them to add support diagnostics: ANONYMOUS_FUNCTION_PARAMETER_WITH_DEFAULT_VALUE USELESS_VARARG_ON_PARAMETER
This commit is contained in:
committed by
Mikhail Glukhikh
parent
33b7c68a21
commit
8b4f2b269c
+18
@@ -1231,6 +1231,24 @@ public class LazyBodyIsNotTouchedTilContractsPhaseTestGenerated extends Abstract
|
||||
public void testValOnAnnotationParameter() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/valOnAnnotationParameter.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/diagnostics/functionAsExpression")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class FunctionAsExpression extends AbstractLazyBodyIsNotTouchedTilContractsPhaseTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInFunctionAsExpression() throws Exception {
|
||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/diagnostics/functionAsExpression"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@TestMetadata("Parameters.kt")
|
||||
public void testParameters() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/functionAsExpression/Parameters.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/expresssions")
|
||||
|
||||
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
FILE: Parameters.kt
|
||||
public final val bar: R|(kotlin/Int) -> kotlin/Unit| = fun <anonymous>(p: R|kotlin/Int| = Int(3)): R|kotlin/Unit| {
|
||||
}
|
||||
|
||||
public get(): R|(kotlin/Int) -> kotlin/Unit|
|
||||
public final val bas: R|(kotlin/Int) -> kotlin/Unit| = fun <anonymous>(vararg p: R|kotlin/Int|): R|kotlin/Unit| {
|
||||
}
|
||||
|
||||
public get(): R|(kotlin/Int) -> kotlin/Unit|
|
||||
public final fun gar(): R|(kotlin/Int) -> kotlin/Unit| {
|
||||
^gar fun <anonymous>(p: R|kotlin/Int| = Int(3)): R|kotlin/Unit| {
|
||||
}
|
||||
|
||||
}
|
||||
public final fun gas(): R|(kotlin/Int) -> kotlin/Unit| {
|
||||
^gas fun <anonymous>(vararg p: R|kotlin/Int|): R|kotlin/Unit| {
|
||||
}
|
||||
|
||||
}
|
||||
public final fun outer(b: R|kotlin/Any?|): R|kotlin/Unit| {
|
||||
lval bar: R|(kotlin/Int) -> kotlin/Unit| = fun <anonymous>(p: R|kotlin/Int| = Int(3)): R|kotlin/Unit| {
|
||||
}
|
||||
|
||||
lval bas: R|(kotlin/Int) -> kotlin/Unit| = fun <anonymous>(vararg p: R|kotlin/Int|): R|kotlin/Unit| {
|
||||
}
|
||||
|
||||
local final fun gar(): R|(kotlin/Int) -> kotlin/Unit| {
|
||||
^gar fun <anonymous>(p: R|kotlin/Int| = Int(3)): R|kotlin/Unit| {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
local final fun gas(): R|(kotlin/Int) -> kotlin/Unit| {
|
||||
^gas fun <anonymous>(vararg p: R|kotlin/Int|): R|kotlin/Unit| {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
R|/outer|(fun <anonymous>(p: R|kotlin/Int| = Int(3)): R|kotlin/Unit| {
|
||||
}
|
||||
)
|
||||
R|/outer|(fun <anonymous>(vararg p: R|kotlin/Int|): R|kotlin/Unit| {
|
||||
}
|
||||
)
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
val bar = fun(p: Int = <!ANONYMOUS_FUNCTION_PARAMETER_WITH_DEFAULT_VALUE!>3<!>) {}
|
||||
val bas = fun(<!USELESS_VARARG_ON_PARAMETER!>vararg p: Int<!>) {}
|
||||
|
||||
fun gar() = fun(p: Int = <!ANONYMOUS_FUNCTION_PARAMETER_WITH_DEFAULT_VALUE!>3<!>) {}
|
||||
fun gas() = fun(<!USELESS_VARARG_ON_PARAMETER!>vararg p: Int<!>) {}
|
||||
|
||||
fun outer(b: Any?) {
|
||||
val bar = fun(p: Int = <!ANONYMOUS_FUNCTION_PARAMETER_WITH_DEFAULT_VALUE!>3<!>) {}
|
||||
val bas = fun(<!USELESS_VARARG_ON_PARAMETER!>vararg p: Int<!>) {}
|
||||
|
||||
fun gar() = fun(p: Int = <!ANONYMOUS_FUNCTION_PARAMETER_WITH_DEFAULT_VALUE!>3<!>) {}
|
||||
fun gas() = fun(<!USELESS_VARARG_ON_PARAMETER!>vararg p: Int<!>) {}
|
||||
|
||||
outer(fun(p: Int = <!ANONYMOUS_FUNCTION_PARAMETER_WITH_DEFAULT_VALUE!>3<!>) {})
|
||||
outer(fun(<!USELESS_VARARG_ON_PARAMETER!>vararg p: Int<!>) {})
|
||||
}
|
||||
+16
@@ -1424,6 +1424,22 @@ public class FirDiagnosticTestGenerated extends AbstractFirDiagnosticTest {
|
||||
public void testValOnAnnotationParameter() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/valOnAnnotationParameter.kt");
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/diagnostics/functionAsExpression")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
public class FunctionAsExpression extends AbstractFirDiagnosticTest {
|
||||
@Test
|
||||
public void testAllFilesPresentInFunctionAsExpression() throws Exception {
|
||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/diagnostics/functionAsExpression"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("Parameters.kt")
|
||||
public void testParameters() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/functionAsExpression/Parameters.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
||||
+17
@@ -1433,6 +1433,23 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos
|
||||
public void testValOnAnnotationParameter() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/valOnAnnotationParameter.kt");
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/diagnostics/functionAsExpression")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@Execution(ExecutionMode.SAME_THREAD)
|
||||
public class FunctionAsExpression extends AbstractFirDiagnosticsWithLightTreeTest {
|
||||
@Test
|
||||
public void testAllFilesPresentInFunctionAsExpression() throws Exception {
|
||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/diagnostics/functionAsExpression"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("Parameters.kt")
|
||||
public void testParameters() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/functionAsExpression/Parameters.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.extended.report
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
|
||||
import org.jetbrains.kotlin.fir.expressions.FirStatement
|
||||
|
||||
object FirAnonymousFunctionChecker : FirExpressionChecker<FirStatement>() {
|
||||
override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
if (expression !is FirAnonymousFunction) {
|
||||
return
|
||||
}
|
||||
for (valueParameter in expression.valueParameters) {
|
||||
val source = valueParameter.source ?: continue
|
||||
if (valueParameter.defaultValue != null) {
|
||||
reporter.report(source, FirErrors.ANONYMOUS_FUNCTION_PARAMETER_WITH_DEFAULT_VALUE)
|
||||
}
|
||||
if (valueParameter.isVararg) {
|
||||
reporter.report(source, FirErrors.USELESS_VARARG_ON_PARAMETER)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -10,11 +10,16 @@ import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirExpressionChecke
|
||||
import org.jetbrains.kotlin.fir.analysis.checkersComponent
|
||||
import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
|
||||
class ExpressionCheckersDiagnosticComponent(collector: AbstractDiagnosticCollector) : AbstractDiagnosticCollectorComponent(collector) {
|
||||
private val checkers = session.checkersComponent.expressionCheckers
|
||||
|
||||
override fun visitAnonymousFunction(anonymousFunction: FirAnonymousFunction, data: CheckerContext) {
|
||||
checkers.basicExpressionCheckers.check(anonymousFunction, data, reporter)
|
||||
}
|
||||
|
||||
override fun visitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall, data: CheckerContext) {
|
||||
checkers.basicExpressionCheckers.check(typeOperatorCall, data, reporter)
|
||||
}
|
||||
|
||||
+8
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_SUPER_CA
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.AMBIGUITY
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ANNOTATION_CLASS_MEMBER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ANONYMOUS_FUNCTION_PARAMETER_WITH_DEFAULT_VALUE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ANY_METHOD_IMPLEMENTED_IN_INTERFACE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ARRAY_EQUALITY_OPERATOR_CAN_BE_REPLACED_WITH_EQUALS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ASSIGNED_VALUE_IS_NEVER_READ
|
||||
@@ -133,6 +134,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNRESOLVED_LABEL
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNRESOLVED_REFERENCE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNUSED_VARIABLE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UPPER_BOUND_VIOLATED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USELESS_VARARG_ON_PARAMETER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARIABLE_EXPECTED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARIABLE_INITIALIZER_IS_REDUNDANT
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARIABLE_NEVER_READ
|
||||
@@ -360,6 +362,12 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
|
||||
|
||||
map.put(FUNCTION_DECLARATION_WITH_NO_NAME, "Function declaration must have a name")
|
||||
|
||||
map.put(
|
||||
ANONYMOUS_FUNCTION_PARAMETER_WITH_DEFAULT_VALUE,
|
||||
"An anonymous function is not allowed to specify default values for its parameters"
|
||||
)
|
||||
map.put(USELESS_VARARG_ON_PARAMETER, "Vararg on this parameter is useless")
|
||||
|
||||
// Properties & accessors
|
||||
map.put(
|
||||
ABSTRACT_PROPERTY_IN_NON_ABSTRACT_CLASS,
|
||||
|
||||
@@ -158,6 +158,10 @@ object FirErrors {
|
||||
|
||||
val FUNCTION_DECLARATION_WITH_NO_NAME by error0<FirSourceElement, KtFunction>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE)
|
||||
|
||||
// TODO: val ANONYMOUS_FUNCTION_WITH_NAME by error1<FirSourceElement, PsiElement, Name>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||
val ANONYMOUS_FUNCTION_PARAMETER_WITH_DEFAULT_VALUE by error0<FirSourceElement, KtParameter>(SourceElementPositioningStrategies.PARAMETER_DEFAULT_VALUE)
|
||||
val USELESS_VARARG_ON_PARAMETER by warning0<FirSourceElement, KtParameter>()
|
||||
|
||||
// Properties & accessors
|
||||
val ABSTRACT_PROPERTY_IN_NON_ABSTRACT_CLASS by error2<FirSourceElement, KtModifierListOwner, FirMemberDeclaration, FirMemberDeclaration>(SourceElementPositioningStrategies.MODALITY_MODIFIER)
|
||||
val PRIVATE_PROPERTY_IN_INTERFACE by error0<FirSourceElement, KtProperty>(SourceElementPositioningStrategies.VISIBILITY_MODIFIER)
|
||||
|
||||
+38
@@ -247,6 +247,30 @@ object LightTreePositioningStrategies {
|
||||
return markElement(tree.operationReference(node) ?: node, startOffset, endOffset, tree, node)
|
||||
}
|
||||
}
|
||||
|
||||
val PARAMETER_DEFAULT_VALUE: LightTreePositioningStrategy = object : LightTreePositioningStrategy() {
|
||||
override fun mark(
|
||||
node: LighterASTNode,
|
||||
startOffset: Int,
|
||||
endOffset: Int,
|
||||
tree: FlyweightCapableTreeStructure<LighterASTNode>
|
||||
): List<TextRange> {
|
||||
val defaultValueElement = tree.defaultValue(node) ?: node
|
||||
return markElement(defaultValueElement, startOffset, endOffset, tree, node)
|
||||
}
|
||||
}
|
||||
|
||||
val PARAMETER_VARARG_MODIFIER: LightTreePositioningStrategy = object : LightTreePositioningStrategy() {
|
||||
override fun mark(
|
||||
node: LighterASTNode,
|
||||
startOffset: Int,
|
||||
endOffset: Int,
|
||||
tree: FlyweightCapableTreeStructure<LighterASTNode>
|
||||
): List<TextRange> {
|
||||
val modifier = tree.modifierList(node)?.let { modifierList -> tree.findChildByType(modifierList, KtTokens.VARARG_KEYWORD) }
|
||||
return markElement(modifier ?: node, startOffset, endOffset, tree, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun FirSourceElement.hasValOrVar(): Boolean =
|
||||
@@ -317,6 +341,20 @@ private fun FlyweightCapableTreeStructure<LighterASTNode>.receiverTypeReference(
|
||||
}
|
||||
}
|
||||
|
||||
private fun FlyweightCapableTreeStructure<LighterASTNode>.defaultValue(node: LighterASTNode): LighterASTNode? {
|
||||
val childrenRef = Ref<Array<LighterASTNode?>>()
|
||||
getChildren(node, childrenRef)
|
||||
// p : T = v
|
||||
return childrenRef.get()?.reversed()?.firstOrNull {
|
||||
it != null &&
|
||||
it.tokenType != KtTokens.WHITE_SPACE &&
|
||||
it.tokenType != KtTokens.EQ &&
|
||||
it.tokenType != KtNodeTypes.TYPE_REFERENCE &&
|
||||
it.tokenType != KtTokens.COLON &&
|
||||
it.tokenType != KtTokens.IDENTIFIER
|
||||
}
|
||||
}
|
||||
|
||||
fun FlyweightCapableTreeStructure<LighterASTNode>.findChildByType(node: LighterASTNode, type: IElementType): LighterASTNode? {
|
||||
val childrenRef = Ref<Array<LighterASTNode?>>()
|
||||
getChildren(node, childrenRef)
|
||||
|
||||
+10
@@ -52,4 +52,14 @@ object SourceElementPositioningStrategies {
|
||||
LightTreePositioningStrategies.OPERATOR,
|
||||
PositioningStrategies.OPERATOR
|
||||
)
|
||||
|
||||
val PARAMETER_DEFAULT_VALUE = SourceElementPositioningStrategy(
|
||||
LightTreePositioningStrategies.PARAMETER_DEFAULT_VALUE,
|
||||
PositioningStrategies.PARAMETER_DEFAULT_VALUE
|
||||
)
|
||||
|
||||
val PARAMETER_VARARG_MODIFIER = SourceElementPositioningStrategy(
|
||||
LightTreePositioningStrategies.PARAMETER_VARARG_MODIFIER,
|
||||
PositioningStrategies.PARAMETER_VARARG_MODIFIER
|
||||
)
|
||||
}
|
||||
+4
@@ -8,6 +8,10 @@ package org.jetbrains.kotlin.fir.checkers
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.expression.*
|
||||
|
||||
object CommonExpressionCheckers : ExpressionCheckers() {
|
||||
override val basicExpressionCheckers: Set<FirBasicExpressionChecker> = setOf(
|
||||
FirAnonymousFunctionChecker,
|
||||
)
|
||||
|
||||
override val qualifiedAccessCheckers: Set<FirQualifiedAccessChecker> = setOf(
|
||||
FirSuperNotAvailableChecker,
|
||||
FirNotASupertypeChecker,
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_ANONYMOUS_PARAMETER -UNUSED_VARIABLE
|
||||
|
||||
val bar = fun(p: Int = 3) {}
|
||||
val bas = fun(vararg p: Int) {}
|
||||
|
||||
fun gar() = fun(p: Int = 3) {}
|
||||
fun gas() = fun(vararg p: Int) {}
|
||||
|
||||
fun outer(b: Any?) {
|
||||
val bar = fun(p: Int = 3) {}
|
||||
val bas = fun(vararg p: Int) {}
|
||||
|
||||
fun gar() = fun(p: Int = 3) {}
|
||||
fun gas() = fun(vararg p: Int) {}
|
||||
|
||||
outer(fun(p: Int = 3) {})
|
||||
outer(fun(vararg p: Int) {})
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_ANONYMOUS_PARAMETER -UNUSED_VARIABLE
|
||||
|
||||
val bar = fun(p: Int = <!ANONYMOUS_FUNCTION_PARAMETER_WITH_DEFAULT_VALUE!>3<!>) {}
|
||||
|
||||
Vendored
+1
-1
@@ -10,7 +10,7 @@ class A {
|
||||
fun f3(a0: Int, vararg a1: Foo) {
|
||||
fun f4(vararg a: Foo) {}
|
||||
|
||||
val g = fun (vararg v: Foo) {}
|
||||
val g = fun (<!USELESS_VARARG_ON_PARAMETER!>vararg v: Foo<!>) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -16,7 +16,7 @@ class A {
|
||||
fun f3(a0: Int, vararg a1: Foo) {
|
||||
fun f4(vararg a: Foo) {}
|
||||
|
||||
val g = fun (vararg v: Foo) {}
|
||||
val g = fun (<!USELESS_VARARG_ON_PARAMETER!>vararg v: Foo<!>) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user