[FIR] Prohibit statement-like expressions in expression context

^KT-61067 Fixed
This commit is contained in:
Nikolay Lunyak
2023-08-11 14:56:02 +03:00
parent 5c19cb3fcb
commit b25a67a50e
19 changed files with 183 additions and 91 deletions
@@ -93,6 +93,12 @@ public class DiagnosticCompilerTestFE10TestdataTestGenerated extends AbstractDia
runTest("compiler/testData/diagnostics/tests/AssignToArrayElement.kt");
}
@Test
@TestMetadata("assigningAssignments.kt")
public void testAssigningAssignments() throws Exception {
runTest("compiler/testData/diagnostics/tests/assigningAssignments.kt");
}
@Test
@TestMetadata("AutoCreatedIt.kt")
public void testAutoCreatedIt() throws Exception {
@@ -93,6 +93,12 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated
runTest("compiler/testData/diagnostics/tests/AssignToArrayElement.kt");
}
@Test
@TestMetadata("assigningAssignments.kt")
public void testAssigningAssignments() throws Exception {
runTest("compiler/testData/diagnostics/tests/assigningAssignments.kt");
}
@Test
@TestMetadata("AutoCreatedIt.kt")
public void testAutoCreatedIt() throws Exception {
@@ -93,6 +93,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir
runTest("compiler/testData/diagnostics/tests/AssignToArrayElement.kt");
}
@Test
@TestMetadata("assigningAssignments.kt")
public void testAssigningAssignments() throws Exception {
runTest("compiler/testData/diagnostics/tests/assigningAssignments.kt");
}
@Test
@TestMetadata("AutoCreatedIt.kt")
public void testAutoCreatedIt() throws Exception {
@@ -93,6 +93,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia
runTest("compiler/testData/diagnostics/tests/AssignToArrayElement.kt");
}
@Test
@TestMetadata("assigningAssignments.kt")
public void testAssigningAssignments() throws Exception {
runTest("compiler/testData/diagnostics/tests/assigningAssignments.kt");
}
@Test
@TestMetadata("AutoCreatedIt.kt")
public void testAutoCreatedIt() throws Exception {
@@ -143,7 +143,7 @@ class LightTreeRawFirDeclarationBuilder(
DESTRUCTURING_DECLARATION -> container += convertDestructingDeclaration(node).toFirDestructingDeclaration(baseModuleData)
TYPEALIAS -> container += convertTypeAlias(node) as FirStatement
CLASS_INITIALIZER -> container += convertAnonymousInitializer(node) as FirStatement
else -> if (node.isExpression()) container += expressionConverter.getAsFirExpression<FirStatement>(node)
else -> if (node.isExpression()) container += expressionConverter.getAsFirStatement(node)
}
}
return FirBlockBuilder().apply {
@@ -1839,7 +1839,7 @@ class LightTreeRawFirDeclarationBuilder(
if (block == null) return buildEmptyExpressionBlock()
if (block.tokenType != BLOCK) {
return FirSingleExpressionBlock(
expressionConverter.getAsFirExpression(block)
expressionConverter.getAsFirStatement(block)
)
}
@@ -60,15 +60,41 @@ class LightTreeRawFirExpressionBuilder(
context: Context<LighterASTNode> = Context(),
) : AbstractLightTreeRawFirBuilder(session, tree, context) {
inline fun <reified R : FirElement> getAsFirExpression(expression: LighterASTNode?, errorReason: String = ""): R {
inline fun <reified R : FirExpression> getAsFirExpression(
expression: LighterASTNode?,
errorReason: String = "",
sourceWhenInvalidExpression: LighterASTNode? = expression,
isValidExpression: (R) -> Boolean = { !it.isStatementLikeExpression },
): R {
val converted = expression?.let { convertExpression(it, errorReason) }
return converted as? R
?: buildErrorExpression(
expression?.toFirSourceElement(),
return when {
converted is R -> when {
isValidExpression(converted) -> converted
else -> buildErrorExpression(
sourceWhenInvalidExpression?.toFirSourceElement(),
ConeSimpleDiagnostic(errorReason, DiagnosticKind.ExpressionExpected),
converted,
)
}
else -> buildErrorExpression(
converted?.source?.withForcedKindFrom(context) ?: expression?.toFirSourceElement(),
if (expression == null) ConeSyntaxDiagnostic(errorReason)
else ConeSimpleDiagnostic(errorReason, DiagnosticKind.ExpressionExpected),
converted,
) as R
)
} as R
}
fun getAsFirStatement(expression: LighterASTNode, errorReason: String = ""): FirStatement {
return when (val converted = convertExpression(expression, errorReason)) {
is FirStatement -> converted
else -> buildErrorExpression(
expression.toFirSourceElement(),
ConeSimpleDiagnostic(errorReason, DiagnosticKind.ExpressionExpected),
converted,
)
}
}
/***** EXPRESSIONS *****/
@@ -316,7 +342,14 @@ class LightTreeRawFirExpressionBuilder(
firOperation,
leftArgAsFir.annotations,
rightArg,
) { getAsFirExpression(this) }
) {
getAsFirExpression<FirExpression>(
this,
"Incorrect expression in assignment: ${binaryExpression.asText}",
sourceWhenInvalidExpression = binaryExpression,
isValidExpression = { !it.isStatementLikeExpression || it.isArraySet },
)
}
} else {
buildEqualityOperatorCall {
source = binaryExpression.toFirSourceElement()
@@ -379,7 +412,7 @@ class LightTreeRawFirExpressionBuilder(
}
BLOCK -> firExpression = declarationBuilder.convertBlock(it)
PROPERTY -> firExpression = declarationBuilder.convertPropertyDeclaration(it)
else -> if (it.isExpression()) firExpression = getAsFirExpression(it)
else -> if (it.isExpression()) firExpression = getAsFirStatement(it)
}
}
@@ -457,7 +490,7 @@ class LightTreeRawFirExpressionBuilder(
BLOCK -> firExpression = declarationBuilder.convertBlockExpression(it)
else -> if (it.isExpression()) {
context.forwardLabelUsagePermission(annotatedExpression, it)
firExpression = getAsFirExpression(it)
firExpression = getAsFirStatement(it)
}
}
}
@@ -1209,10 +1242,10 @@ class LightTreeRawFirExpressionBuilder(
if (it.getChildNodeByType(BLOCK) != null) {
firBlock = getAsFirExpression(it)
} else {
firStatement = getAsFirExpression(it)
firStatement = getAsFirStatement(it)
}
}
else -> if (it.isExpression()) firStatement = getAsFirExpression(it)
else -> if (it.isExpression()) firStatement = getAsFirStatement(it)
}
}
@@ -47,10 +47,7 @@ import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import org.jetbrains.kotlin.utils.addToStdlib.runUnless
import org.jetbrains.kotlin.utils.addToStdlib.shouldNotBeCalled
import org.jetbrains.kotlin.utils.addToStdlib.*
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
import org.jetbrains.kotlin.utils.exceptions.requireWithAttachment
import org.jetbrains.kotlin.utils.exceptions.withPsiEntry
@@ -297,28 +294,33 @@ open class PsiRawFirBuilder(
): FirExpression = toFirExpression { ConeSimpleDiagnostic(errorReason, kind) }
private inline fun KtElement?.toFirExpression(
sourceWhenThisIsNull: PsiElement? = null,
sourceWhenThisIsNull: KtElement? = null,
sourceWhenInvalidExpression: KtElement? = this,
isValidExpression: (FirExpression) -> Boolean = { !it.isStatementLikeExpression },
diagnosticFn: () -> ConeDiagnostic,
): FirExpression {
if (this == null) {
return buildErrorExpression(source = sourceWhenThisIsNull?.toFirSourceElement(), diagnosticFn())
}
val result = when (val fir = convertElement(this, null)) {
is FirExpression -> fir
else -> {
return buildErrorExpression {
return when (val fir = convertElement(this, null)) {
is FirExpression -> when {
isValidExpression(fir) -> checkSelectorInvariant(fir)
else -> buildErrorExpression {
nonExpressionElement = fir
diagnostic = diagnosticFn()
source = toFirSourceElement()
source = sourceWhenInvalidExpression?.toFirSourceElement()
}
}
else -> buildErrorExpression {
nonExpressionElement = fir
diagnostic = diagnosticFn()
source = fir?.source?.withForcedKindFrom(this@PsiRawFirBuilder.context) ?: toFirSourceElement()
}
}
return toFirExpression(result)
}
private fun KtElement.toFirExpression(result: FirExpression): FirExpression {
private fun KtElement.checkSelectorInvariant(result: FirExpression): FirExpression {
val callExpressionCallee = (this as? KtCallExpression)?.calleeExpression?.unwrapParenthesesLabelsAndAnnotations()
if (this is KtNameReferenceExpression ||
@@ -2700,7 +2702,15 @@ open class PsiRawFirBuilder(
leftArgument.annotations,
expression.right,
) {
(this as KtExpression).toFirExpression("Incorrect expression in assignment: ${expression.text}")
(this as KtExpression).toFirExpression(
sourceWhenInvalidExpression = expression,
isValidExpression = { !it.isStatementLikeExpression || it.isArraySet },
) {
ConeSimpleDiagnostic(
"Incorrect expression in assignment: ${expression.text}",
DiagnosticKind.ExpressionExpected
)
}
}
} else {
buildEqualityOperatorCall {
@@ -2926,11 +2936,7 @@ open class PsiRawFirBuilder(
override fun visitParenthesizedExpression(expression: KtParenthesizedExpression, data: FirElement?): FirElement {
context.forwardLabelUsagePermission(expression, expression.expression)
return expression.expression?.accept(this, data)
?: buildErrorExpression(
expression.toFirSourceElement(),
ConeSyntaxDiagnostic("Empty parentheses")
)
return expression.expression.toFirExpression("Empty parentheses")
}
override fun visitLabeledExpression(expression: KtLabeledExpression, data: FirElement?): FirElement {
@@ -51,6 +51,7 @@ import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.realElement
import org.jetbrains.kotlin.types.ConstantValueKind
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.expressions.OperatorConventions
@@ -696,3 +697,11 @@ fun AnnotationUseSiteTarget?.appliesToPrimaryConstructorParameter() = this == nu
this == AnnotationUseSiteTarget.CONSTRUCTOR_PARAMETER ||
this == AnnotationUseSiteTarget.RECEIVER ||
this == AnnotationUseSiteTarget.FILE
fun KtSourceElement.withForcedKindFrom(context: Context<*>): KtSourceElement {
return when (val forcedKind = context.forcedElementSourceKind) {
kind -> this
is KtFakeSourceElementKind -> fakeElement(forcedKind)
else -> this.realElement()
}
}
@@ -47,7 +47,6 @@ import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
import org.jetbrains.kotlin.types.ConstantValueKind
import org.jetbrains.kotlin.types.SmartcastStability
import org.jetbrains.kotlin.types.model.safeSubstitute
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
import kotlin.contracts.ExperimentalContracts
@@ -490,7 +489,7 @@ fun FirSafeCallExpression.propagateTypeFromQualifiedAccessAfterNullCheck(
val selector = selector
val resultingType = when {
selector is FirExpression && !selector.isCallToStatementLikeFunction -> {
selector is FirExpression && !selector.isStatementLikeExpression -> {
val type = selector.coneTypeSafe<ConeKotlinType>() ?: return
type.withNullability(ConeNullability.NULLABLE, session.typeContext)
}
@@ -506,12 +505,6 @@ fun FirSafeCallExpression.propagateTypeFromQualifiedAccessAfterNullCheck(
session.lookupTracker?.recordTypeResolveAsLookup(independentInstance, source, file.source)
}
private val FirExpression.isCallToStatementLikeFunction: Boolean
get() {
val symbol = (this as? FirFunctionCall)?.calleeReference?.toResolvedNamedFunctionSymbol() ?: return false
return origin == FirFunctionCallOrigin.Operator && symbol.name in OperatorNameConventions.STATEMENT_LIKE_OPERATORS
}
fun FirAnnotation.getCorrespondingClassSymbolOrNull(session: FirSession): FirRegularClassSymbol? {
return annotationTypeRef.coneType.fullyExpandedType(session).toRegularClassSymbol(session)
}
@@ -17,12 +17,16 @@ import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass
import org.jetbrains.kotlin.fir.declarations.utils.isStatic
import org.jetbrains.kotlin.fir.expressions.FirBlock
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirFunctionCallOrigin
import org.jetbrains.kotlin.fir.renderer.FirRenderer
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.builder.*
import org.jetbrains.kotlin.fir.types.impl.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.util.OperatorNameConventions.STATEMENT_LIKE_OPERATORS
import org.jetbrains.kotlin.util.wrapIntoFileAnalysisExceptionIfNeeded
import org.jetbrains.kotlin.util.wrapIntoSourceCodeAnalysisExceptionIfNeeded
@@ -254,3 +258,18 @@ fun <T> List<T>.smartPlus(other: List<T>): List<T> = when {
// Source element may be missing if the class came from a library
fun FirVariable.isEnumEntries(containingClass: FirClass) = isStatic && name == StandardNames.ENUM_ENTRIES && containingClass.isEnumClass
val FirExpression.isArraySet: Boolean
get() {
val name = (this as? FirFunctionCall)?.calleeReference?.name ?: return false
return origin == FirFunctionCallOrigin.Operator && name == OperatorNameConventions.SET
}
val FirExpression.isStatementLikeExpression: Boolean
get() = when (this) {
is FirFunctionCall -> origin == FirFunctionCallOrigin.Operator && calleeReference.name in STATEMENT_LIKE_OPERATORS
else -> isIndexedAssignment
}
private val FirExpression.isIndexedAssignment: Boolean
get() = this is FirBlock && statements.lastOrNull()?.source?.kind == KtFakeSourceElementKind.ImplicitUnit.IndexedAssignmentCoercion
@@ -0,0 +1,12 @@
// ISSUE: KT-61067
// DIAGNOSTICS: -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE
fun main() {
val storages: HashMap<String, String> = HashMap<String, String>()
val a = <!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>storages<!NO_SET_METHOD!>["4"]<!> = ""<!> //K1 compile error - Kotlin: Assignments are not expressions, and only expressions are allowed in this context
storages<!NO_SET_METHOD!>["4"]<!> = ""
var nonStorages: Int = 10
val b = <!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>nonStorages = 20<!>
nonStorages = 20
}
@@ -0,0 +1,12 @@
// ISSUE: KT-61067
// DIAGNOSTICS: -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE
fun main() {
val storages: HashMap<String, String> = HashMap<String, String>()
val a = <!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>storages<!NO_SET_METHOD!><!UNRESOLVED_REFERENCE!>[<!>"4"<!UNRESOLVED_REFERENCE!>]<!><!> = ""<!> //K1 compile error - Kotlin: Assignments are not expressions, and only expressions are allowed in this context
storages<!NO_SET_METHOD!><!UNRESOLVED_REFERENCE!>[<!>"4"<!UNRESOLVED_REFERENCE!>]<!><!> = ""
var nonStorages: Int = 10
val b = <!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>nonStorages = 20<!>
nonStorages = 20
}
@@ -1,3 +1,3 @@
fun test() {
(d@ val bar = 2)
(d@ <!EXPRESSION_EXPECTED!>val bar = 2<!>)
}
@@ -1,9 +1,9 @@
fun foo() {
class A
<!CONFLICTING_OVERLOADS!>fun bar()<!> {}
(<!CONFLICTING_OVERLOADS!>fun bar()<!> {})
<!CONFLICTING_OVERLOADS!>fun A.foo()<!> {}
(<!CONFLICTING_OVERLOADS!>fun A.foo()<!> {})
fun bar() {}
(<!ANONYMOUS_FUNCTION_WITH_NAME!>fun bar() {}<!>)
fun A.foo() {}
(<!ANONYMOUS_FUNCTION_WITH_NAME!>fun A.foo() {}<!>)
<!NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>run<!>(<!ANONYMOUS_FUNCTION_WITH_NAME!>fun foo() {}<!>)
}
@@ -1,23 +0,0 @@
//KT-629 Assignments are parsed as expressions.
package kt629
class A() {
var p = "yeah"
operator fun rem(other : A) : A {
return A();
}
}
fun box() : Boolean {
var c = A()
val d = c;
c %= A();
return (c != d) && <!EXPRESSION_EXPECTED!>(c.p = "yeah")<!>
}
fun box2() : Boolean {
var c = A()
return <!EXPRESSION_EXPECTED!>(c.p = "yeah")<!> && true
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
//KT-629 Assignments are parsed as expressions.
package kt629
@@ -122,12 +122,12 @@ fun testWithSubject_bad_4(b: B) {
}
// also bad
when (b) {
<!EXPRESSION_EXPECTED!>(x = b)<!> -> {}
<!EXPRESSION_EXPECTED!>(b += b)<!> -> {}
<!EXPRESSION_EXPECTED!>(b -= b)<!> -> {}
<!EXPRESSION_EXPECTED!>(b *= b)<!> -> {}
<!EXPRESSION_EXPECTED!>(b /= b)<!> -> {}
<!EXPRESSION_EXPECTED!>(b %= b)<!> -> {}
(<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>x = b<!>) -> {}
(<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b += b<!>) -> {}
(<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b -= b<!>) -> {}
(<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b *= b<!>) -> {}
(<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b /= b<!>) -> {}
(<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b %= b<!>) -> {}
}
}
@@ -226,11 +226,11 @@ fun testWithRange_bad_4(b: B) {
}
// also bad
when (b) {
in <!EXPRESSION_EXPECTED!>(x = b)<!> -> {}
in <!EXPRESSION_EXPECTED!>(b += b)<!> -> {}
in <!EXPRESSION_EXPECTED!>(b -= b)<!> -> {}
in <!EXPRESSION_EXPECTED!>(b *= b)<!> -> {}
in <!EXPRESSION_EXPECTED!>(b /= b)<!> -> {}
in <!EXPRESSION_EXPECTED!>(b %= b)<!> -> {}
in (<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>x = b<!>) -> {}
in (<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b += b<!>) -> {}
in (<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b -= b<!>) -> {}
in (<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b *= b<!>) -> {}
in (<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b /= b<!>) -> {}
in (<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b %= b<!>) -> {}
}
}
@@ -122,12 +122,12 @@ fun testWithSubject_bad_4(b: B) {
}
// also bad
when (b) {
<!EXPRESSION_EXPECTED!>(x = b)<!> -> {}
<!EXPRESSION_EXPECTED!>(b += b)<!> -> {}
<!EXPRESSION_EXPECTED!>(b -= b)<!> -> {}
<!EXPRESSION_EXPECTED!>(b *= b)<!> -> {}
<!EXPRESSION_EXPECTED!>(b /= b)<!> -> {}
<!EXPRESSION_EXPECTED!>(b %= b)<!> -> {}
(<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>x = b<!>) -> {}
(<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b += b<!>) -> {}
(<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b -= b<!>) -> {}
(<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b *= b<!>) -> {}
(<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b /= b<!>) -> {}
(<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b %= b<!>) -> {}
}
}
@@ -226,11 +226,11 @@ fun testWithRange_bad_4(b: B) {
}
// also bad
when (b) {
in <!EXPRESSION_EXPECTED!>(x = b)<!> -> {}
in <!EXPRESSION_EXPECTED!>(b += b)<!> -> {}
in <!EXPRESSION_EXPECTED!>(b -= b)<!> -> {}
in <!EXPRESSION_EXPECTED!>(b *= b)<!> -> {}
in <!EXPRESSION_EXPECTED!>(b /= b)<!> -> {}
in <!EXPRESSION_EXPECTED!>(b %= b)<!> -> {}
in (<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>x = b<!>) -> {}
in (<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b += b<!>) -> {}
in (<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b -= b<!>) -> {}
in (<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b *= b<!>) -> {}
in (<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b /= b<!>) -> {}
in (<!ASSIGNMENT_IN_EXPRESSION_CONTEXT!>b %= b<!>) -> {}
}
}
@@ -93,6 +93,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/AssignToArrayElement.kt");
}
@Test
@TestMetadata("assigningAssignments.kt")
public void testAssigningAssignments() throws Exception {
runTest("compiler/testData/diagnostics/tests/assigningAssignments.kt");
}
@Test
@TestMetadata("AutoCreatedIt.kt")
public void testAutoCreatedIt() throws Exception {