FIR/FIR IDE: Use entire FirVariableAssignment when reporting UNSAFE_CALL
(e.g., `nullable.a = b`), and use positioning strategies to locate the dot in the LHS expression. Without it, only the callee reference is reported on, which makes the highlighting of the error and application of quickfixes incorrect in the IDE. Also fixed issue with annotated and/or labeled expressions on LHS of assignment (e.g., `(@Ann label@ i) = 34`).
This commit is contained in:
committed by
Ilya Kirillov
parent
1d9247ae0f
commit
d2b8204fdc
+2
-1
@@ -58,7 +58,7 @@ enum class PositioningStrategy(private val strategy: String? = null) {
|
|||||||
VALUE_ARGUMENTS,
|
VALUE_ARGUMENTS,
|
||||||
SUPERTYPES_LIST,
|
SUPERTYPES_LIST,
|
||||||
RETURN_WITH_LABEL,
|
RETURN_WITH_LABEL,
|
||||||
ASSIGNMENT_VALUE,
|
PROPERTY_INITIALIZER,
|
||||||
WHOLE_ELEMENT,
|
WHOLE_ELEMENT,
|
||||||
INT_LITERAL_OUT_OF_RANGE,
|
INT_LITERAL_OUT_OF_RANGE,
|
||||||
FLOAT_LITERAL_OUT_OF_RANGE,
|
FLOAT_LITERAL_OUT_OF_RANGE,
|
||||||
@@ -71,6 +71,7 @@ enum class PositioningStrategy(private val strategy: String? = null) {
|
|||||||
RESERVED_UNDERSCORE,
|
RESERVED_UNDERSCORE,
|
||||||
QUESTION_MARK_BY_TYPE,
|
QUESTION_MARK_BY_TYPE,
|
||||||
ANNOTATION_USE_SITE,
|
ANNOTATION_USE_SITE,
|
||||||
|
ASSIGNMENT_LHS,
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -55,7 +55,7 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
|
|||||||
val ASSIGNMENT_IN_EXPRESSION_CONTEXT by error<KtBinaryExpression>()
|
val ASSIGNMENT_IN_EXPRESSION_CONTEXT by error<KtBinaryExpression>()
|
||||||
val BREAK_OR_CONTINUE_OUTSIDE_A_LOOP by error<PsiElement>()
|
val BREAK_OR_CONTINUE_OUTSIDE_A_LOOP by error<PsiElement>()
|
||||||
val NOT_A_LOOP_LABEL by error<PsiElement>()
|
val NOT_A_LOOP_LABEL by error<PsiElement>()
|
||||||
val VARIABLE_EXPECTED by error<PsiElement>()
|
val VARIABLE_EXPECTED by error<PsiElement>(PositioningStrategy.ASSIGNMENT_LHS)
|
||||||
val DELEGATION_IN_INTERFACE by error<PsiElement>()
|
val DELEGATION_IN_INTERFACE by error<PsiElement>()
|
||||||
val NESTED_CLASS_NOT_ALLOWED by error<KtNamedDeclaration>(PositioningStrategy.DECLARATION_NAME) {
|
val NESTED_CLASS_NOT_ALLOWED by error<KtNamedDeclaration>(PositioningStrategy.DECLARATION_NAME) {
|
||||||
parameter<String>("declaration")
|
parameter<String>("declaration")
|
||||||
@@ -629,7 +629,7 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
|
|||||||
parameter<ConeKotlinType>("expectedType")
|
parameter<ConeKotlinType>("expectedType")
|
||||||
parameter<ConeKotlinType>("actualType")
|
parameter<ConeKotlinType>("actualType")
|
||||||
}
|
}
|
||||||
val INITIALIZER_TYPE_MISMATCH by error<KtProperty>(PositioningStrategy.ASSIGNMENT_VALUE) {
|
val INITIALIZER_TYPE_MISMATCH by error<KtProperty>(PositioningStrategy.PROPERTY_INITIALIZER) {
|
||||||
parameter<ConeKotlinType>("expectedType")
|
parameter<ConeKotlinType>("expectedType")
|
||||||
parameter<ConeKotlinType>("actualType")
|
parameter<ConeKotlinType>("actualType")
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -87,7 +87,7 @@ object FirErrors {
|
|||||||
val ASSIGNMENT_IN_EXPRESSION_CONTEXT by error0<KtBinaryExpression>()
|
val ASSIGNMENT_IN_EXPRESSION_CONTEXT by error0<KtBinaryExpression>()
|
||||||
val BREAK_OR_CONTINUE_OUTSIDE_A_LOOP by error0<PsiElement>()
|
val BREAK_OR_CONTINUE_OUTSIDE_A_LOOP by error0<PsiElement>()
|
||||||
val NOT_A_LOOP_LABEL by error0<PsiElement>()
|
val NOT_A_LOOP_LABEL by error0<PsiElement>()
|
||||||
val VARIABLE_EXPECTED by error0<PsiElement>()
|
val VARIABLE_EXPECTED by error0<PsiElement>(SourceElementPositioningStrategies.ASSIGNMENT_LHS)
|
||||||
val DELEGATION_IN_INTERFACE by error0<PsiElement>()
|
val DELEGATION_IN_INTERFACE by error0<PsiElement>()
|
||||||
val NESTED_CLASS_NOT_ALLOWED by error1<KtNamedDeclaration, String>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
val NESTED_CLASS_NOT_ALLOWED by error1<KtNamedDeclaration, String>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||||
val INCORRECT_CHARACTER_LITERAL by error0<PsiElement>()
|
val INCORRECT_CHARACTER_LITERAL by error0<PsiElement>()
|
||||||
@@ -384,7 +384,7 @@ object FirErrors {
|
|||||||
val CONST_VAL_WITHOUT_INITIALIZER by error0<KtProperty>(SourceElementPositioningStrategies.CONST_MODIFIER)
|
val CONST_VAL_WITHOUT_INITIALIZER by error0<KtProperty>(SourceElementPositioningStrategies.CONST_MODIFIER)
|
||||||
val CONST_VAL_WITH_NON_CONST_INITIALIZER by error0<KtExpression>()
|
val CONST_VAL_WITH_NON_CONST_INITIALIZER by error0<KtExpression>()
|
||||||
val WRONG_SETTER_PARAMETER_TYPE by error2<KtTypeReference, ConeKotlinType, ConeKotlinType>()
|
val WRONG_SETTER_PARAMETER_TYPE by error2<KtTypeReference, ConeKotlinType, ConeKotlinType>()
|
||||||
val INITIALIZER_TYPE_MISMATCH by error2<KtProperty, ConeKotlinType, ConeKotlinType>(SourceElementPositioningStrategies.ASSIGNMENT_VALUE)
|
val INITIALIZER_TYPE_MISMATCH by error2<KtProperty, ConeKotlinType, ConeKotlinType>(SourceElementPositioningStrategies.PROPERTY_INITIALIZER)
|
||||||
val GETTER_VISIBILITY_DIFFERS_FROM_PROPERTY_VISIBILITY by error0<KtModifierListOwner>(SourceElementPositioningStrategies.VISIBILITY_MODIFIER)
|
val GETTER_VISIBILITY_DIFFERS_FROM_PROPERTY_VISIBILITY by error0<KtModifierListOwner>(SourceElementPositioningStrategies.VISIBILITY_MODIFIER)
|
||||||
val SETTER_VISIBILITY_INCONSISTENT_WITH_PROPERTY_VISIBILITY by error0<KtModifierListOwner>(SourceElementPositioningStrategies.VISIBILITY_MODIFIER)
|
val SETTER_VISIBILITY_INCONSISTENT_WITH_PROPERTY_VISIBILITY by error0<KtModifierListOwner>(SourceElementPositioningStrategies.VISIBILITY_MODIFIER)
|
||||||
val WRONG_SETTER_RETURN_TYPE by error0<KtTypeReference>()
|
val WRONG_SETTER_RETURN_TYPE by error0<KtTypeReference>()
|
||||||
|
|||||||
+2
-2
@@ -47,9 +47,9 @@ class ErrorNodeDiagnosticCollectorComponent(
|
|||||||
override fun visitErrorNamedReference(errorNamedReference: FirErrorNamedReference, data: CheckerContext) {
|
override fun visitErrorNamedReference(errorNamedReference: FirErrorNamedReference, data: CheckerContext) {
|
||||||
val source = errorNamedReference.source ?: return
|
val source = errorNamedReference.source ?: return
|
||||||
val qualifiedAccessOrAnnotationCall = data.qualifiedAccessOrAnnotationCalls.lastOrNull()?.takeIf {
|
val qualifiedAccessOrAnnotationCall = data.qualifiedAccessOrAnnotationCalls.lastOrNull()?.takeIf {
|
||||||
// Use the source of the enclosing FirQualifiedAccessExpression if it is exactly the call to the erroneous callee.
|
// Use the source of the enclosing FirQualifiedAccess if it is exactly the call to the erroneous callee.
|
||||||
when (it) {
|
when (it) {
|
||||||
is FirQualifiedAccessExpression -> it.calleeReference == errorNamedReference
|
is FirQualifiedAccess -> it.calleeReference == errorNamedReference
|
||||||
is FirAnnotationCall -> it.calleeReference == errorNamedReference
|
is FirAnnotationCall -> it.calleeReference == errorNamedReference
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|||||||
+58
@@ -425,6 +425,13 @@ object LightTreePositioningStrategies {
|
|||||||
endOffset: Int,
|
endOffset: Int,
|
||||||
tree: FlyweightCapableTreeStructure<LighterASTNode>
|
tree: FlyweightCapableTreeStructure<LighterASTNode>
|
||||||
): List<TextRange> {
|
): List<TextRange> {
|
||||||
|
if (node.tokenType == KtNodeTypes.BINARY_EXPRESSION &&
|
||||||
|
tree.findDescendantByTypes(node, KtTokens.ALL_ASSIGNMENTS) != null
|
||||||
|
) {
|
||||||
|
tree.findDescendantByType(node, KtNodeTypes.DOT_QUALIFIED_EXPRESSION)?.let {
|
||||||
|
return markElement(tree.dotOperator(it) ?: it, startOffset, endOffset, tree, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (node.tokenType == KtNodeTypes.DOT_QUALIFIED_EXPRESSION) {
|
if (node.tokenType == KtNodeTypes.DOT_QUALIFIED_EXPRESSION) {
|
||||||
return markElement(tree.dotOperator(node) ?: node, startOffset, endOffset, tree, node)
|
return markElement(tree.dotOperator(node) ?: node, startOffset, endOffset, tree, node)
|
||||||
}
|
}
|
||||||
@@ -707,6 +714,33 @@ object LightTreePositioningStrategies {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ASSIGNMENT_LHS: LightTreePositioningStrategy = object : LightTreePositioningStrategy() {
|
||||||
|
override fun mark(
|
||||||
|
node: LighterASTNode,
|
||||||
|
startOffset: Int,
|
||||||
|
endOffset: Int,
|
||||||
|
tree: FlyweightCapableTreeStructure<LighterASTNode>
|
||||||
|
): List<TextRange> {
|
||||||
|
if ((node.tokenType == KtNodeTypes.BINARY_EXPRESSION &&
|
||||||
|
tree.findDescendantByTypes(node, KtTokens.ALL_ASSIGNMENTS) != null) ||
|
||||||
|
((node.tokenType == KtNodeTypes.PREFIX_EXPRESSION || node.tokenType == KtNodeTypes.POSTFIX_EXPRESSION) &&
|
||||||
|
tree.findDescendantByTypes(node, KtTokens.INCREMENT_AND_DECREMENT) != null)
|
||||||
|
) {
|
||||||
|
val lhs = if (node.tokenType == KtNodeTypes.PREFIX_EXPRESSION) {
|
||||||
|
tree.lastChildExpression(node)
|
||||||
|
} else {
|
||||||
|
tree.firstChildExpression(node)
|
||||||
|
}
|
||||||
|
lhs?.let {
|
||||||
|
tree.unwrapParenthesesLabelsAndAnnotations(it)?.let { unwrapped ->
|
||||||
|
return markElement(unwrapped, startOffset, endOffset, tree, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.mark(node, startOffset, endOffset, tree)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val ANNOTATION_USE_SITE: LightTreePositioningStrategy = object : LightTreePositioningStrategy() {
|
val ANNOTATION_USE_SITE: LightTreePositioningStrategy = object : LightTreePositioningStrategy() {
|
||||||
override fun mark(
|
override fun mark(
|
||||||
node: LighterASTNode,
|
node: LighterASTNode,
|
||||||
@@ -810,6 +844,18 @@ private fun FlyweightCapableTreeStructure<LighterASTNode>.referenceExpression(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun FlyweightCapableTreeStructure<LighterASTNode>.unwrapParenthesesLabelsAndAnnotations(node: LighterASTNode): LighterASTNode? {
|
||||||
|
var unwrapped = node
|
||||||
|
while (true) {
|
||||||
|
unwrapped = when (unwrapped.tokenType) {
|
||||||
|
KtNodeTypes.PARENTHESIZED -> firstChildExpression(unwrapped) ?: return unwrapped
|
||||||
|
KtNodeTypes.LABELED_EXPRESSION -> lastChildExpression(unwrapped) ?: return unwrapped
|
||||||
|
KtNodeTypes.ANNOTATED_EXPRESSION -> firstChildExpression(unwrapped) ?: return unwrapped
|
||||||
|
else -> return unwrapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun FlyweightCapableTreeStructure<LighterASTNode>.findExpressionDeep(node: LighterASTNode): LighterASTNode? =
|
private fun FlyweightCapableTreeStructure<LighterASTNode>.findExpressionDeep(node: LighterASTNode): LighterASTNode? =
|
||||||
findFirstDescendant(node) { it.isExpression() }
|
findFirstDescendant(node) { it.isExpression() }
|
||||||
|
|
||||||
@@ -898,6 +944,18 @@ fun FlyweightCapableTreeStructure<LighterASTNode>.selector(node: LighterASTNode)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun FlyweightCapableTreeStructure<LighterASTNode>.firstChildExpression(node: LighterASTNode): LighterASTNode? {
|
||||||
|
val childrenRef = Ref<Array<LighterASTNode?>>()
|
||||||
|
getChildren(node, childrenRef)
|
||||||
|
return childrenRef.get()?.firstOrNull { it?.isExpression() == true }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FlyweightCapableTreeStructure<LighterASTNode>.lastChildExpression(node: LighterASTNode): LighterASTNode? {
|
||||||
|
val childrenRef = Ref<Array<LighterASTNode?>>()
|
||||||
|
getChildren(node, childrenRef)
|
||||||
|
return childrenRef.get()?.lastOrNull { it?.isExpression() == true }
|
||||||
|
}
|
||||||
|
|
||||||
fun FlyweightCapableTreeStructure<LighterASTNode>.findChildByType(node: LighterASTNode, type: IElementType): LighterASTNode? {
|
fun FlyweightCapableTreeStructure<LighterASTNode>.findChildByType(node: LighterASTNode, type: IElementType): LighterASTNode? {
|
||||||
val childrenRef = Ref<Array<LighterASTNode?>>()
|
val childrenRef = Ref<Array<LighterASTNode?>>()
|
||||||
getChildren(node, childrenRef)
|
getChildren(node, childrenRef)
|
||||||
|
|||||||
+7
-2
@@ -208,9 +208,9 @@ object SourceElementPositioningStrategies {
|
|||||||
PositioningStrategies.RETURN_WITH_LABEL
|
PositioningStrategies.RETURN_WITH_LABEL
|
||||||
)
|
)
|
||||||
|
|
||||||
val ASSIGNMENT_VALUE = SourceElementPositioningStrategy(
|
val PROPERTY_INITIALIZER = SourceElementPositioningStrategy(
|
||||||
LightTreePositioningStrategies.LAST_CHILD,
|
LightTreePositioningStrategies.LAST_CHILD,
|
||||||
PositioningStrategies.ASSIGNMENT_VALUE
|
PositioningStrategies.PROPERTY_INITIALIZER
|
||||||
)
|
)
|
||||||
|
|
||||||
val WHOLE_ELEMENT = SourceElementPositioningStrategy(
|
val WHOLE_ELEMENT = SourceElementPositioningStrategy(
|
||||||
@@ -247,4 +247,9 @@ object SourceElementPositioningStrategies {
|
|||||||
LightTreePositioningStrategies.ANNOTATION_USE_SITE,
|
LightTreePositioningStrategies.ANNOTATION_USE_SITE,
|
||||||
PositioningStrategies.ANNOTATION_USE_SITE
|
PositioningStrategies.ANNOTATION_USE_SITE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val ASSIGNMENT_LHS = SourceElementPositioningStrategy(
|
||||||
|
LightTreePositioningStrategies.ASSIGNMENT_LHS,
|
||||||
|
PositioningStrategies.ASSIGNMENT_LHS
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-5
@@ -83,20 +83,31 @@ abstract class AbstractRawFirBuilderTestCase : KtParsingTestCase(
|
|||||||
if (!result.add(this)) {
|
if (!result.add(this)) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
propertyLoop@ for (property in this::class.memberProperties) {
|
for (property in this::class.memberProperties) {
|
||||||
val childElement = property.getter.apply { isAccessible = true }.call(this)
|
if (hasNoAcceptAndTransform(this::class.simpleName, property.name)) continue
|
||||||
|
|
||||||
when (childElement) {
|
when (val childElement = property.getter.apply { isAccessible = true }.call(this)) {
|
||||||
is FirNoReceiverExpression -> continue@propertyLoop
|
is FirNoReceiverExpression -> continue
|
||||||
is FirElement -> childElement.traverseChildren(result)
|
is FirElement -> childElement.traverseChildren(result)
|
||||||
is List<*> -> childElement.filterIsInstance<FirElement>().forEach { it.traverseChildren(result) }
|
is List<*> -> childElement.filterIsInstance<FirElement>().forEach { it.traverseChildren(result) }
|
||||||
else -> continue@propertyLoop
|
else -> continue
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val firImplClassPropertiesWithNoAcceptAndTransform = mapOf(
|
||||||
|
"FirResolvedImportImpl" to "delegate",
|
||||||
|
"FirErrorTypeRefImpl" to "delegatedTypeRef",
|
||||||
|
"FirResolvedTypeRefImpl" to "delegatedTypeRef"
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun hasNoAcceptAndTransform(className: String?, propertyName: String): Boolean {
|
||||||
|
if (className == null) return false
|
||||||
|
return firImplClassPropertiesWithNoAcceptAndTransform[className] == propertyName
|
||||||
|
}
|
||||||
|
|
||||||
private fun FirFile.visitChildren(): Set<FirElement> =
|
private fun FirFile.visitChildren(): Set<FirElement> =
|
||||||
ConsistencyVisitor().let {
|
ConsistencyVisitor().let {
|
||||||
this@visitChildren.accept(it)
|
this@visitChildren.accept(it)
|
||||||
|
|||||||
+36
-39
@@ -445,9 +445,8 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// 1. Support receiver capturing for `array.b++` (elementType == ARRAY_ACCESS_EXPRESSION).
|
// 1. Support receiver capturing for `a?.b++` (elementType == SAFE_ACCESS_EXPRESSION).
|
||||||
// 2. Support receiver capturing for `a?.b++` (elementType == SAFE_ACCESS_EXPRESSION).
|
// 2. Add box test cases for #1 where receiver expression has side effects.
|
||||||
// 3. Add box test cases for #1 and #2 where receiver expression has side effects.
|
|
||||||
fun generateIncrementOrDecrementBlock(
|
fun generateIncrementOrDecrementBlock(
|
||||||
baseExpression: T,
|
baseExpression: T,
|
||||||
operationReference: T?,
|
operationReference: T?,
|
||||||
@@ -456,21 +455,8 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
|
|||||||
prefix: Boolean,
|
prefix: Boolean,
|
||||||
convert: T.() -> FirExpression
|
convert: T.() -> FirExpression
|
||||||
): FirExpression {
|
): FirExpression {
|
||||||
// NOTE: By removing surrounding parentheses and labels, FirLabels will NOT be created for those labels.
|
val unwrappedArgument = argument.unwrap() ?: return buildErrorExpression {
|
||||||
// This should be fine since the label is meaningless and unusable for a ++/-- argument.
|
diagnostic = ConeSimpleDiagnostic("Inc/dec without operand", DiagnosticKind.Syntax)
|
||||||
var unwrappedArgument = argument
|
|
||||||
while (true) {
|
|
||||||
unwrappedArgument = when (unwrappedArgument?.elementType) {
|
|
||||||
PARENTHESIZED -> unwrappedArgument?.getExpressionInParentheses()
|
|
||||||
LABELED_EXPRESSION -> unwrappedArgument?.getLabeledExpression()
|
|
||||||
else -> break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unwrappedArgument == null) {
|
|
||||||
return buildErrorExpression {
|
|
||||||
diagnostic = ConeSimpleDiagnostic("Inc/dec without operand", DiagnosticKind.Syntax)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unwrappedArgument.elementType == DOT_QUALIFIED_EXPRESSION) {
|
if (unwrappedArgument.elementType == DOT_QUALIFIED_EXPRESSION) {
|
||||||
@@ -566,6 +552,20 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun T?.unwrap(): T? {
|
||||||
|
// NOTE: By removing surrounding parentheses and labels, FirLabels will NOT be created for those labels.
|
||||||
|
// This should be fine since the label is meaningless and unusable for a ++/-- argument or assignment LHS.
|
||||||
|
var unwrapped = this
|
||||||
|
while (true) {
|
||||||
|
unwrapped = when (unwrapped?.elementType) {
|
||||||
|
PARENTHESIZED -> unwrapped?.getExpressionInParentheses()
|
||||||
|
LABELED_EXPRESSION -> unwrapped?.getLabeledExpression()
|
||||||
|
ANNOTATED_EXPRESSION -> unwrapped?.getAnnotatedExpression()
|
||||||
|
else -> return unwrapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* given:
|
* given:
|
||||||
* a.b++
|
* a.b++
|
||||||
@@ -860,12 +860,6 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PARENTHESIZED -> {
|
|
||||||
return initializeLValue(left.getExpressionInParentheses(), convertQualified)
|
|
||||||
}
|
|
||||||
ANNOTATED_EXPRESSION -> {
|
|
||||||
return initializeLValue(left.getAnnotatedExpression(), convertQualified)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buildErrorNamedReference {
|
return buildErrorNamedReference {
|
||||||
@@ -881,19 +875,19 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
|
|||||||
operation: FirOperation,
|
operation: FirOperation,
|
||||||
convert: T.() -> FirExpression
|
convert: T.() -> FirExpression
|
||||||
): FirStatement {
|
): FirStatement {
|
||||||
val tokenType = this?.elementType
|
val unwrappedLhs = this.unwrap() ?: return buildErrorExpression {
|
||||||
if (tokenType == PARENTHESIZED) {
|
diagnostic = ConeSimpleDiagnostic("Inc/dec without operand", DiagnosticKind.Syntax)
|
||||||
return this!!.getExpressionInParentheses().generateAssignment(baseSource, rhs, value, operation, convert)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val tokenType = unwrappedLhs.elementType
|
||||||
if (tokenType == ARRAY_ACCESS_EXPRESSION) {
|
if (tokenType == ARRAY_ACCESS_EXPRESSION) {
|
||||||
require(this != null)
|
|
||||||
if (operation == FirOperation.ASSIGN) {
|
if (operation == FirOperation.ASSIGN) {
|
||||||
context.arraySetArgument[this] = value
|
context.arraySetArgument[unwrappedLhs] = value
|
||||||
}
|
}
|
||||||
return if (operation == FirOperation.ASSIGN) {
|
return if (operation == FirOperation.ASSIGN) {
|
||||||
this.convert()
|
unwrappedLhs.convert()
|
||||||
} else {
|
} else {
|
||||||
generateAugmentedArraySetCall(baseSource, operation, rhs, convert)
|
generateAugmentedArraySetCall(unwrappedLhs, baseSource, operation, rhs, convert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -924,7 +918,7 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
|
|||||||
return buildVariableAssignment {
|
return buildVariableAssignment {
|
||||||
source = baseSource
|
source = baseSource
|
||||||
rValue = value
|
rValue = value
|
||||||
calleeReference = initializeLValue(this@generateAssignment) { convert() as? FirQualifiedAccess }
|
calleeReference = initializeLValue(unwrappedLhs) { convert() as? FirQualifiedAccess }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -950,7 +944,8 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
|
|||||||
return safeCallNonAssignment
|
return safeCallNonAssignment
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun T.generateAugmentedArraySetCall(
|
private fun generateAugmentedArraySetCall(
|
||||||
|
unwrappedReceiver: T,
|
||||||
baseSource: FirSourceElement?,
|
baseSource: FirSourceElement?,
|
||||||
operation: FirOperation,
|
operation: FirOperation,
|
||||||
rhs: T?,
|
rhs: T?,
|
||||||
@@ -959,12 +954,13 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
|
|||||||
return buildAugmentedArraySetCall {
|
return buildAugmentedArraySetCall {
|
||||||
source = baseSource
|
source = baseSource
|
||||||
this.operation = operation
|
this.operation = operation
|
||||||
assignCall = generateAugmentedCallForAugmentedArraySetCall(operation, rhs, convert)
|
assignCall = generateAugmentedCallForAugmentedArraySetCall(unwrappedReceiver, operation, rhs, convert)
|
||||||
setGetBlock = generateSetGetBlockForAugmentedArraySetCall(baseSource, operation, rhs, convert)
|
setGetBlock = generateSetGetBlockForAugmentedArraySetCall(unwrappedReceiver, baseSource, operation, rhs, convert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun T.generateAugmentedCallForAugmentedArraySetCall(
|
private fun generateAugmentedCallForAugmentedArraySetCall(
|
||||||
|
unwrappedReceiver: T,
|
||||||
operation: FirOperation,
|
operation: FirOperation,
|
||||||
rhs: T?,
|
rhs: T?,
|
||||||
convert: T.() -> FirExpression
|
convert: T.() -> FirExpression
|
||||||
@@ -977,7 +973,7 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
|
|||||||
calleeReference = buildSimpleNamedReference {
|
calleeReference = buildSimpleNamedReference {
|
||||||
name = FirOperationNameConventions.ASSIGNMENTS.getValue(operation)
|
name = FirOperationNameConventions.ASSIGNMENTS.getValue(operation)
|
||||||
}
|
}
|
||||||
explicitReceiver = convert()
|
explicitReceiver = unwrappedReceiver.convert()
|
||||||
argumentList = buildArgumentList {
|
argumentList = buildArgumentList {
|
||||||
arguments += rhs?.convert() ?: buildErrorExpression(
|
arguments += rhs?.convert() ?: buildErrorExpression(
|
||||||
null,
|
null,
|
||||||
@@ -989,7 +985,8 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun T.generateSetGetBlockForAugmentedArraySetCall(
|
private fun generateSetGetBlockForAugmentedArraySetCall(
|
||||||
|
unwrappedReceiver: T,
|
||||||
baseSource: FirSourceElement?,
|
baseSource: FirSourceElement?,
|
||||||
operation: FirOperation,
|
operation: FirOperation,
|
||||||
rhs: T?,
|
rhs: T?,
|
||||||
@@ -1005,7 +1002,7 @@ abstract class BaseFirBuilder<T>(val baseSession: FirSession, val context: Conte
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
return buildBlock {
|
return buildBlock {
|
||||||
val baseCall = convert() as FirFunctionCall
|
val baseCall = unwrappedReceiver.convert() as FirFunctionCall
|
||||||
|
|
||||||
val arrayVariable = generateTemporaryVariable(
|
val arrayVariable = generateTemporaryVariable(
|
||||||
baseModuleData,
|
baseModuleData,
|
||||||
|
|||||||
@@ -775,11 +775,14 @@ object PositioningStrategies {
|
|||||||
|
|
||||||
val DOT_BY_QUALIFIED: PositioningStrategy<PsiElement> = object : PositioningStrategy<PsiElement>() {
|
val DOT_BY_QUALIFIED: PositioningStrategy<PsiElement> = object : PositioningStrategy<PsiElement>() {
|
||||||
override fun mark(element: PsiElement): List<TextRange> {
|
override fun mark(element: PsiElement): List<TextRange> {
|
||||||
when (element) {
|
if (element is KtBinaryExpression && element.operationToken in KtTokens.ALL_ASSIGNMENTS) {
|
||||||
is KtDotQualifiedExpression -> {
|
element.left?.let { left ->
|
||||||
return mark(element.operationTokenNode.psi)
|
left.findDescendantOfType<KtDotQualifiedExpression>()?.let { return mark(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (element is KtDotQualifiedExpression) {
|
||||||
|
return mark(element.operationTokenNode.psi)
|
||||||
|
}
|
||||||
// Fallback to mark the callee reference.
|
// Fallback to mark the callee reference.
|
||||||
return REFERENCE_BY_QUALIFIED.mark(element)
|
return REFERENCE_BY_QUALIFIED.mark(element)
|
||||||
}
|
}
|
||||||
@@ -848,9 +851,9 @@ object PositioningStrategies {
|
|||||||
|
|
||||||
val REIFIED_MODIFIER: PositioningStrategy<KtModifierListOwner> = modifierSetPosition(KtTokens.REIFIED_KEYWORD)
|
val REIFIED_MODIFIER: PositioningStrategy<KtModifierListOwner> = modifierSetPosition(KtTokens.REIFIED_KEYWORD)
|
||||||
|
|
||||||
val ASSIGNMENT_VALUE: PositioningStrategy<KtProperty> = object : PositioningStrategy<PsiElement>() {
|
val PROPERTY_INITIALIZER: PositioningStrategy<KtProperty> = object : PositioningStrategy<KtProperty>() {
|
||||||
override fun mark(element: PsiElement): List<TextRange> {
|
override fun mark(element: KtProperty): List<TextRange> {
|
||||||
return markElement(if (element is KtProperty) element.initializer ?: element else element)
|
return markElement(element.initializer ?: element)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -871,6 +874,17 @@ object PositioningStrategies {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ASSIGNMENT_LHS: PositioningStrategy<PsiElement> = object : PositioningStrategy<PsiElement>() {
|
||||||
|
override fun mark(element: PsiElement): List<TextRange> {
|
||||||
|
if (element is KtBinaryExpression && element.operationToken in KtTokens.ALL_ASSIGNMENTS) {
|
||||||
|
element.left.let { left -> left.unwrapParenthesesLabelsAndAnnotations()?.let { return markElement(it) } }
|
||||||
|
}
|
||||||
|
if (element is KtUnaryExpression && element.operationToken in KtTokens.INCREMENT_AND_DECREMENT) {
|
||||||
|
element.baseExpression.let { arg -> arg.unwrapParenthesesLabelsAndAnnotations()?.let { return markElement(it) } }
|
||||||
|
}
|
||||||
|
return super.mark(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param locateReferencedName whether to remove any nested parentheses while locating the reference element. This is useful for
|
* @param locateReferencedName whether to remove any nested parentheses while locating the reference element. This is useful for
|
||||||
|
|||||||
@@ -265,4 +265,5 @@ public interface KtTokens {
|
|||||||
|
|
||||||
TokenSet AUGMENTED_ASSIGNMENTS = TokenSet.create(PLUSEQ, MINUSEQ, MULTEQ, PERCEQ, DIVEQ);
|
TokenSet AUGMENTED_ASSIGNMENTS = TokenSet.create(PLUSEQ, MINUSEQ, MULTEQ, PERCEQ, DIVEQ);
|
||||||
TokenSet ALL_ASSIGNMENTS = TokenSet.create(EQ, PLUSEQ, MINUSEQ, MULTEQ, PERCEQ, DIVEQ);
|
TokenSet ALL_ASSIGNMENTS = TokenSet.create(EQ, PLUSEQ, MINUSEQ, MULTEQ, PERCEQ, DIVEQ);
|
||||||
|
TokenSet INCREMENT_AND_DECREMENT = TokenSet.create(PLUSPLUS, MINUSMINUS);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -498,4 +498,16 @@ fun KtExpression.isNull(): Boolean {
|
|||||||
returns(true) implies (this@isNull is KtConstantExpression)
|
returns(true) implies (this@isNull is KtConstantExpression)
|
||||||
}
|
}
|
||||||
return this is KtConstantExpression && this.node.elementType == KtNodeTypes.NULL
|
return this is KtConstantExpression && this.node.elementType == KtNodeTypes.NULL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun PsiElement?.unwrapParenthesesLabelsAndAnnotations(): PsiElement? {
|
||||||
|
var unwrapped = this
|
||||||
|
while (true) {
|
||||||
|
unwrapped = when (unwrapped) {
|
||||||
|
is KtParenthesizedExpression -> unwrapped.expression
|
||||||
|
is KtLabeledExpression -> unwrapped.baseExpression
|
||||||
|
is KtAnnotatedExpression -> unwrapped.baseExpression
|
||||||
|
else -> return unwrapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+26
-7
@@ -51,18 +51,25 @@ fun cannotBe() {
|
|||||||
<!VARIABLE_EXPECTED!>5<!> = 34
|
<!VARIABLE_EXPECTED!>5<!> = 34
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
@Target(AnnotationTarget.EXPRESSION)
|
||||||
|
annotation class Ann
|
||||||
|
|
||||||
fun canBe(i0: Int, j: Int) {
|
fun canBe(i0: Int, j: Int) {
|
||||||
var i = i0
|
var i = i0
|
||||||
(<!VARIABLE_EXPECTED!>label@ i<!>) = 34
|
(label@ i) = 34
|
||||||
|
|
||||||
(<!VARIABLE_EXPECTED!>label@ j<!>) = 34 //repeat for j
|
(label@ j) = 34 //repeat for j
|
||||||
|
|
||||||
val a = A()
|
val a = A()
|
||||||
(<!VARIABLE_EXPECTED!>l@ a.a<!>) = 3894
|
(l@ a.a) = 3894
|
||||||
|
|
||||||
|
@Ann
|
||||||
|
l@ (i) = 123
|
||||||
}
|
}
|
||||||
|
|
||||||
fun canBe2(j: Int) {
|
fun canBe2(j: Int) {
|
||||||
(<!VARIABLE_EXPECTED!>label@ j<!>) = 34
|
(label@ j) = 34
|
||||||
}
|
}
|
||||||
|
|
||||||
class A() {
|
class A() {
|
||||||
@@ -78,10 +85,13 @@ class Test() {
|
|||||||
(f@ <!VARIABLE_EXPECTED!>getInt()<!>) += 343
|
(f@ <!VARIABLE_EXPECTED!>getInt()<!>) += 343
|
||||||
|
|
||||||
<!VARIABLE_EXPECTED!>1<!>++
|
<!VARIABLE_EXPECTED!>1<!>++
|
||||||
(r@ <!VARIABLE_EXPECTED!>1<!>)++
|
(r@ <!VARIABLE_EXPECTED!>1<!>)--
|
||||||
|
|
||||||
<!VARIABLE_EXPECTED!>getInt()<!>++
|
<!VARIABLE_EXPECTED!>getInt()<!>++
|
||||||
(m@ <!VARIABLE_EXPECTED!>getInt()<!>)++
|
(m@ <!VARIABLE_EXPECTED!>getInt()<!>)--
|
||||||
|
|
||||||
|
++<!VARIABLE_EXPECTED!>2<!>
|
||||||
|
--(r@ <!VARIABLE_EXPECTED!>2<!>)
|
||||||
|
|
||||||
this<!UNRESOLVED_REFERENCE!>++<!>
|
this<!UNRESOLVED_REFERENCE!>++<!>
|
||||||
|
|
||||||
@@ -89,6 +99,9 @@ class Test() {
|
|||||||
s += "ss"
|
s += "ss"
|
||||||
s += this
|
s += this
|
||||||
s += (a@ 2)
|
s += (a@ 2)
|
||||||
|
|
||||||
|
@Ann
|
||||||
|
l@ (<!VARIABLE_EXPECTED!>1<!>) = 123
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testIncompleteSyntax() {
|
fun testIncompleteSyntax() {
|
||||||
@@ -106,8 +119,11 @@ class Test() {
|
|||||||
<!VAL_REASSIGNMENT!>b<!> += 34
|
<!VAL_REASSIGNMENT!>b<!> += 34
|
||||||
|
|
||||||
a++
|
a++
|
||||||
(l@ a)++
|
(@Ann l@ a)--
|
||||||
(a)++
|
(a)++
|
||||||
|
--a
|
||||||
|
++(@Ann l@ a)
|
||||||
|
--(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testVariables1() {
|
fun testVariables1() {
|
||||||
@@ -122,6 +138,9 @@ class Test() {
|
|||||||
a[3] = 4
|
a[3] = 4
|
||||||
a[4]++
|
a[4]++
|
||||||
a[6] += 43
|
a[6] += 43
|
||||||
|
@Ann
|
||||||
|
a[7] = 7
|
||||||
|
(@Ann l@ (a))[8] = 8
|
||||||
|
|
||||||
ab.getArray()[54] = 23
|
ab.getArray()[54] = 23
|
||||||
ab.getArray()[54]++
|
ab.getArray()[54]++
|
||||||
|
|||||||
+22
-3
@@ -51,6 +51,10 @@ fun cannotBe() {
|
|||||||
<!VARIABLE_EXPECTED!>5<!> = 34
|
<!VARIABLE_EXPECTED!>5<!> = 34
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
@Target(AnnotationTarget.EXPRESSION)
|
||||||
|
annotation class Ann
|
||||||
|
|
||||||
fun canBe(i0: Int, j: Int) {
|
fun canBe(i0: Int, j: Int) {
|
||||||
var <!ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE!>i<!> = i0
|
var <!ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE!>i<!> = i0
|
||||||
(label@ i) = 34
|
(label@ i) = 34
|
||||||
@@ -59,6 +63,9 @@ fun canBe(i0: Int, j: Int) {
|
|||||||
|
|
||||||
val a = A()
|
val a = A()
|
||||||
(l@ a.a) = 3894
|
(l@ a.a) = 3894
|
||||||
|
|
||||||
|
@Ann
|
||||||
|
l@ (i) = 123
|
||||||
}
|
}
|
||||||
|
|
||||||
fun canBe2(j: Int) {
|
fun canBe2(j: Int) {
|
||||||
@@ -78,10 +85,13 @@ class Test() {
|
|||||||
(f@ <!VARIABLE_EXPECTED!>getInt()<!>) += 343
|
(f@ <!VARIABLE_EXPECTED!>getInt()<!>) += 343
|
||||||
|
|
||||||
<!VARIABLE_EXPECTED!>1<!>++
|
<!VARIABLE_EXPECTED!>1<!>++
|
||||||
(<!REDUNDANT_LABEL_WARNING!>r@<!> <!VARIABLE_EXPECTED!>1<!>)++
|
(<!REDUNDANT_LABEL_WARNING!>r@<!> <!VARIABLE_EXPECTED!>1<!>)--
|
||||||
|
|
||||||
<!VARIABLE_EXPECTED!>getInt()<!>++
|
<!VARIABLE_EXPECTED!>getInt()<!>++
|
||||||
(<!REDUNDANT_LABEL_WARNING!>m@<!> <!VARIABLE_EXPECTED!>getInt()<!>)++
|
(<!REDUNDANT_LABEL_WARNING!>m@<!> <!VARIABLE_EXPECTED!>getInt()<!>)--
|
||||||
|
|
||||||
|
++<!VARIABLE_EXPECTED!>2<!>
|
||||||
|
--(<!REDUNDANT_LABEL_WARNING!>r@<!> <!VARIABLE_EXPECTED!>2<!>)
|
||||||
|
|
||||||
this<!UNRESOLVED_REFERENCE!>++<!>
|
this<!UNRESOLVED_REFERENCE!>++<!>
|
||||||
|
|
||||||
@@ -89,6 +99,9 @@ class Test() {
|
|||||||
s += "ss"
|
s += "ss"
|
||||||
s += this
|
s += this
|
||||||
s += (<!REDUNDANT_LABEL_WARNING!>a@<!> 2)
|
s += (<!REDUNDANT_LABEL_WARNING!>a@<!> 2)
|
||||||
|
|
||||||
|
@Ann
|
||||||
|
l@ (<!VARIABLE_EXPECTED!>1<!>) = 123
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testIncompleteSyntax() {
|
fun testIncompleteSyntax() {
|
||||||
@@ -106,8 +119,11 @@ class Test() {
|
|||||||
<!VAL_REASSIGNMENT!>b<!> += 34
|
<!VAL_REASSIGNMENT!>b<!> += 34
|
||||||
|
|
||||||
a++
|
a++
|
||||||
(<!REDUNDANT_LABEL_WARNING!>l@<!> a)++
|
(@Ann <!REDUNDANT_LABEL_WARNING!>l@<!> a)--
|
||||||
(a)++
|
(a)++
|
||||||
|
--a
|
||||||
|
++(@Ann <!REDUNDANT_LABEL_WARNING!>l@<!> a)
|
||||||
|
--(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testVariables1() {
|
fun testVariables1() {
|
||||||
@@ -122,6 +138,9 @@ class Test() {
|
|||||||
a[3] = 4
|
a[3] = 4
|
||||||
a[4]++
|
a[4]++
|
||||||
a[6] += 43
|
a[6] += 43
|
||||||
|
@Ann
|
||||||
|
a[7] = 7
|
||||||
|
(@Ann <!REDUNDANT_LABEL_WARNING!>l@<!> (a))[8] = 8
|
||||||
|
|
||||||
ab.getArray()[54] = 23
|
ab.getArray()[54] = 23
|
||||||
ab.getArray()[54]++
|
ab.getArray()[54]++
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ package lvalue_assignment {
|
|||||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@kotlin.annotation.Retention(value = AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.EXPRESSION}) public final annotation class Ann : kotlin.Annotation {
|
||||||
|
public constructor Ann()
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
|
|
||||||
public open class B {
|
public open class B {
|
||||||
public constructor B()
|
public constructor B()
|
||||||
public final var b: kotlin.Int
|
public final var b: kotlin.Int
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class R {
|
|||||||
fun test() {
|
fun test() {
|
||||||
val o = object {
|
val o = object {
|
||||||
fun run() {
|
fun run() {
|
||||||
<!UNRESOLVED_REFERENCE!>p<!>.<!UNRESOLVED_REFERENCE!>x<!> = 43
|
<!UNRESOLVED_REFERENCE!>p<!>.x = 43
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vendored
+11
@@ -1,6 +1,7 @@
|
|||||||
// !DIAGNOSTICS: -DEBUG_INFO_SMARTCAST
|
// !DIAGNOSTICS: -DEBUG_INFO_SMARTCAST
|
||||||
class Foo {
|
class Foo {
|
||||||
fun foo(a: Foo): Foo = a
|
fun foo(a: Foo): Foo = a
|
||||||
|
var f: Foo? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
@@ -27,4 +28,14 @@ fun main() {
|
|||||||
|
|
||||||
val z: Foo? = null
|
val z: Foo? = null
|
||||||
z!!.foo(z<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>)
|
z!!.foo(z<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>)
|
||||||
|
|
||||||
|
val w: Foo? = null
|
||||||
|
w<!UNSAFE_CALL!>.<!>f = z
|
||||||
|
(w<!UNSAFE_CALL!>.<!>f) = z
|
||||||
|
(label@ w<!UNSAFE_CALL!>.<!>f) = z
|
||||||
|
w!!.f = z
|
||||||
|
w.f = z
|
||||||
|
w<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.f = z
|
||||||
|
w.f<!UNSAFE_CALL!>.<!>f = z
|
||||||
|
w.f!!.f = z
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+11
@@ -1,6 +1,7 @@
|
|||||||
// !DIAGNOSTICS: -DEBUG_INFO_SMARTCAST
|
// !DIAGNOSTICS: -DEBUG_INFO_SMARTCAST
|
||||||
class Foo {
|
class Foo {
|
||||||
fun foo(a: Foo): Foo = a
|
fun foo(a: Foo): Foo = a
|
||||||
|
var f: Foo? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
@@ -27,4 +28,14 @@ fun main() {
|
|||||||
|
|
||||||
val z: Foo? = null
|
val z: Foo? = null
|
||||||
z!!.foo(z<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>)
|
z!!.foo(z<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>)
|
||||||
|
|
||||||
|
val w: Foo? = null
|
||||||
|
w<!UNSAFE_CALL!>.<!>f = z
|
||||||
|
(w<!UNSAFE_CALL!>.<!>f) = z
|
||||||
|
(label@ w<!UNSAFE_CALL!>.<!>f) = z
|
||||||
|
w!!.f = z
|
||||||
|
w.f = z
|
||||||
|
w<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.f = z
|
||||||
|
<!SMARTCAST_IMPOSSIBLE!>w.f<!>.f = z
|
||||||
|
w.f!!.f = z
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
@@ -4,6 +4,7 @@ public fun main(): kotlin.Unit
|
|||||||
|
|
||||||
public final class Foo {
|
public final class Foo {
|
||||||
public constructor Foo()
|
public constructor Foo()
|
||||||
|
public final var f: Foo?
|
||||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
public final fun foo(/*0*/ a: Foo): Foo
|
public final fun foo(/*0*/ a: Foo): Foo
|
||||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
|||||||
+21
-15
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic
|
|||||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol
|
||||||
import org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix
|
import org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.unwrapParenthesesLabelsAndAnnotations
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
|
|
||||||
object AddExclExclCallFixFactories {
|
object AddExclExclCallFixFactories {
|
||||||
@@ -29,46 +30,51 @@ object AddExclExclCallFixFactories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun KtAnalysisSession.getFixForUnsafeCall(psi: PsiElement): List<IntentionAction> {
|
private fun KtAnalysisSession.getFixForUnsafeCall(psi: PsiElement): List<IntentionAction> {
|
||||||
val (target, hasImplicitReceiver) = when (psi) {
|
val (target, hasImplicitReceiver) = when (val unwrapped = psi.unwrapParenthesesLabelsAndAnnotations()) {
|
||||||
// `foo.bar` -> `foo!!.bar`
|
// `foo.bar` -> `foo!!.bar`
|
||||||
is KtDotQualifiedExpression -> psi.receiverExpression to false
|
is KtDotQualifiedExpression -> unwrapped.receiverExpression to false
|
||||||
|
|
||||||
// `foo[bar]` -> `foo!![bar]`
|
// `foo[bar]` -> `foo!![bar]`
|
||||||
is KtArrayAccessExpression -> psi.arrayExpression to false
|
is KtArrayAccessExpression -> unwrapped.arrayExpression to false
|
||||||
|
|
||||||
is KtCallableReferenceExpression -> psi.lhs.let { lhs ->
|
is KtCallableReferenceExpression -> unwrapped.lhs.let { lhs ->
|
||||||
if (lhs != null) {
|
if (lhs != null) {
|
||||||
// `foo::bar` -> `foo!!::bar`
|
// `foo::bar` -> `foo!!::bar`
|
||||||
lhs to false
|
lhs to false
|
||||||
} else {
|
} else {
|
||||||
// `::bar -> this!!::bar`
|
// `::bar -> this!!::bar`
|
||||||
psi to true
|
unwrapped to true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// `bar` -> `this!!.bar`
|
// `bar` -> `this!!.bar`
|
||||||
is KtNameReferenceExpression -> psi to true
|
is KtNameReferenceExpression -> unwrapped to true
|
||||||
|
|
||||||
// `bar()` -> `this!!.bar()`
|
// `bar()` -> `this!!.bar()`
|
||||||
is KtCallExpression -> psi to true
|
is KtCallExpression -> unwrapped to true
|
||||||
|
|
||||||
// `-foo` -> `-foo!!`
|
// `-foo` -> `-foo!!`
|
||||||
// NOTE: Unsafe unary operator call is reported as UNSAFE_CALL, _not_ UNSAFE_OPERATOR_CALL
|
// NOTE: Unsafe unary operator call is reported as UNSAFE_CALL, _not_ UNSAFE_OPERATOR_CALL
|
||||||
is KtUnaryExpression -> psi.baseExpression to false
|
is KtUnaryExpression -> unwrapped.baseExpression to false
|
||||||
|
|
||||||
is KtBinaryExpression -> {
|
is KtBinaryExpression -> {
|
||||||
val receiver = if (KtPsiUtil.isInOrNotInOperation(psi)) {
|
val receiver = when {
|
||||||
// `bar in foo` -> `bar in foo!!`
|
KtPsiUtil.isInOrNotInOperation(unwrapped) ->
|
||||||
psi.right
|
// `bar in foo` -> `bar in foo!!`
|
||||||
} else {
|
unwrapped.right
|
||||||
// `foo + bar` -> `foo!! + bar` OR `foo infixFun bar` -> `foo!! infixFun bar`
|
KtPsiUtil.isAssignment(unwrapped) ->
|
||||||
psi.left
|
// UNSAFE_CALL for assignments (e.g., `foo.bar = value`) is reported on the entire statement (KtBinaryExpression).
|
||||||
|
// The unsafe call is on the LHS of the assignment.
|
||||||
|
return getFixForUnsafeCall(unwrapped.left ?: return emptyList())
|
||||||
|
else ->
|
||||||
|
// `foo + bar` -> `foo!! + bar` OR `foo infixFun bar` -> `foo!! infixFun bar`
|
||||||
|
unwrapped.left
|
||||||
}
|
}
|
||||||
receiver to false
|
receiver to false
|
||||||
}
|
}
|
||||||
|
|
||||||
// UNSAFE_INFIX_CALL/UNSAFE_OPERATOR_CALL on KtBinaryExpression is reported on the child KtOperationReferenceExpression
|
// UNSAFE_INFIX_CALL/UNSAFE_OPERATOR_CALL on KtBinaryExpression is reported on the child KtOperationReferenceExpression
|
||||||
is KtOperationReferenceExpression -> return getFixForUnsafeCall(psi.parent)
|
is KtOperationReferenceExpression -> return getFixForUnsafeCall(unwrapped.parent)
|
||||||
|
|
||||||
else -> return emptyList()
|
else -> return emptyList()
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-6
@@ -11,9 +11,9 @@ import org.jetbrains.kotlin.idea.frontend.api.types.KtTypeNullability
|
|||||||
import org.jetbrains.kotlin.idea.frontend.api.types.KtTypeWithNullability
|
import org.jetbrains.kotlin.idea.frontend.api.types.KtTypeWithNullability
|
||||||
import org.jetbrains.kotlin.idea.quickfix.ReplaceImplicitReceiverCallFix
|
import org.jetbrains.kotlin.idea.quickfix.ReplaceImplicitReceiverCallFix
|
||||||
import org.jetbrains.kotlin.idea.quickfix.ReplaceWithSafeCallFix
|
import org.jetbrains.kotlin.idea.quickfix.ReplaceWithSafeCallFix
|
||||||
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
import org.jetbrains.kotlin.psi.psiUtil.unwrapParenthesesLabelsAndAnnotations
|
||||||
|
|
||||||
object ReplaceCallFixFactories {
|
object ReplaceCallFixFactories {
|
||||||
val unsafeCallFactory =
|
val unsafeCallFactory =
|
||||||
@@ -25,14 +25,23 @@ object ReplaceCallFixFactories {
|
|||||||
return expectedType?.nullability == KtTypeNullability.NON_NULLABLE
|
return expectedType?.nullability == KtTypeNullability.NON_NULLABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
when (val psi = diagnostic.psi) {
|
val psi = diagnostic.psi
|
||||||
is KtDotQualifiedExpression -> listOf(ReplaceWithSafeCallFix(psi, psi.shouldHaveNotNullType()))
|
val target = if (psi is KtBinaryExpression && psi.operationToken in KtTokens.ALL_ASSIGNMENTS) {
|
||||||
|
// UNSAFE_CALL for assignments (e.g., `foo.bar = value`) is reported on the entire statement (KtBinaryExpression).
|
||||||
|
// The unsafe call is on the LHS of the assignment.
|
||||||
|
psi.left
|
||||||
|
} else {
|
||||||
|
psi
|
||||||
|
}.unwrapParenthesesLabelsAndAnnotations()
|
||||||
|
|
||||||
|
when (target) {
|
||||||
|
is KtDotQualifiedExpression -> listOf(ReplaceWithSafeCallFix(target, target.shouldHaveNotNullType()))
|
||||||
is KtNameReferenceExpression -> {
|
is KtNameReferenceExpression -> {
|
||||||
// TODO: As a safety precaution, resolve the expression to determine if it is a call with an implicit receiver.
|
// TODO: As a safety precaution, resolve the expression to determine if it is a call with an implicit receiver.
|
||||||
// This is a defensive check to ensure that the diagnostic was reported on such a call and not some other name reference.
|
// This is a defensive check to ensure that the diagnostic was reported on such a call and not some other name reference.
|
||||||
// This isn't strictly needed because FIR checkers aren't reporting on wrong elements, but ReplaceWithSafeCallFixFactory
|
// This isn't strictly needed because FIR checkers aren't reporting on wrong elements, but ReplaceWithSafeCallFixFactory
|
||||||
// in FE1.0 does so.
|
// in FE1.0 does so.
|
||||||
listOf(ReplaceImplicitReceiverCallFix(psi, psi.shouldHaveNotNullType()))
|
listOf(ReplaceImplicitReceiverCallFix(target, target.shouldHaveNotNullType()))
|
||||||
}
|
}
|
||||||
else -> emptyList()
|
else -> emptyList()
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+15
@@ -229,6 +229,16 @@ public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTes
|
|||||||
runTest("idea/testData/quickfix/addExclExclCall/array4.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/array4.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("assignment.kt")
|
||||||
|
public void testAssignment() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/assignment.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("assignmentToUnsafeCallExpression.kt")
|
||||||
|
public void testAssignmentToUnsafeCallExpression() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/assignmentToUnsafeCallExpression.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("functionReference.kt")
|
@TestMetadata("functionReference.kt")
|
||||||
public void testFunctionReference() throws Exception {
|
public void testFunctionReference() throws Exception {
|
||||||
runTest("idea/testData/quickfix/addExclExclCall/functionReference.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/functionReference.kt");
|
||||||
@@ -1271,6 +1281,11 @@ public class HighLevelQuickFixTestGenerated extends AbstractHighLevelQuickFixTes
|
|||||||
runTest("idea/testData/quickfix/replaceWithSafeCall/assignmentToPropertyWithNoExplicitType.kt");
|
runTest("idea/testData/quickfix/replaceWithSafeCall/assignmentToPropertyWithNoExplicitType.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("assignmentToUnsafeCallExpression.kt")
|
||||||
|
public void testAssignmentToUnsafeCallExpression() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/replaceWithSafeCall/assignmentToUnsafeCallExpression.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("comment.kt")
|
@TestMetadata("comment.kt")
|
||||||
public void testComment() throws Exception {
|
public void testComment() throws Exception {
|
||||||
runTest("idea/testData/quickfix/replaceWithSafeCall/comment.kt");
|
runTest("idea/testData/quickfix/replaceWithSafeCall/comment.kt");
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Add non-null asserted (!!) call" "true"
|
||||||
|
// WITH_RUNTIME
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
fun foo(s: String?) {
|
||||||
|
i = s<caret>.length
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Add non-null asserted (!!) call" "true"
|
||||||
|
// WITH_RUNTIME
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
fun foo(s: String?) {
|
||||||
|
i = s!!.length
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// "Add non-null asserted (!!) call" "true"
|
||||||
|
class A(var s: String)
|
||||||
|
|
||||||
|
fun foo(a: A?) {
|
||||||
|
a<caret>.s = ""
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
// "Add non-null asserted (!!) call" "true"
|
||||||
|
class A(var s: String)
|
||||||
|
|
||||||
|
fun foo(a: A?) {
|
||||||
|
a!!.s = ""
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
// "Replace with safe (?.) call" "true"
|
||||||
|
class A(var s: String? = null)
|
||||||
|
|
||||||
|
fun foo(a: A?) {
|
||||||
|
a<caret>.s = ""
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
// "Replace with safe (?.) call" "true"
|
||||||
|
class A(var s: String? = null)
|
||||||
|
|
||||||
|
fun foo(a: A?) {
|
||||||
|
a?.s = ""
|
||||||
|
}
|
||||||
@@ -642,6 +642,16 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
runTest("idea/testData/quickfix/addExclExclCall/array4.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/array4.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("assignment.kt")
|
||||||
|
public void testAssignment() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/assignment.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("assignmentToUnsafeCallExpression.kt")
|
||||||
|
public void testAssignmentToUnsafeCallExpression() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/addExclExclCall/assignmentToUnsafeCallExpression.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("functionReference.kt")
|
@TestMetadata("functionReference.kt")
|
||||||
public void testFunctionReference() throws Exception {
|
public void testFunctionReference() throws Exception {
|
||||||
runTest("idea/testData/quickfix/addExclExclCall/functionReference.kt");
|
runTest("idea/testData/quickfix/addExclExclCall/functionReference.kt");
|
||||||
@@ -11825,6 +11835,11 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
runTest("idea/testData/quickfix/replaceWithSafeCall/assignmentToPropertyWithNoExplicitType.kt");
|
runTest("idea/testData/quickfix/replaceWithSafeCall/assignmentToPropertyWithNoExplicitType.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("assignmentToUnsafeCallExpression.kt")
|
||||||
|
public void testAssignmentToUnsafeCallExpression() throws Exception {
|
||||||
|
runTest("idea/testData/quickfix/replaceWithSafeCall/assignmentToUnsafeCallExpression.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("comment.kt")
|
@TestMetadata("comment.kt")
|
||||||
public void testComment() throws Exception {
|
public void testComment() throws Exception {
|
||||||
runTest("idea/testData/quickfix/replaceWithSafeCall/comment.kt");
|
runTest("idea/testData/quickfix/replaceWithSafeCall/comment.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user