Tracey Yoshima
12/05/2023, 6:13 PMannotation class SomeAnn
fun foo(example: MutableList<String>) {
@SomeAnn
example += "x"
}
The annotation appears to be fine if it is in line with the statement:
annotation class SomeAnn
fun foo(example: MutableList<String>) {
@SomeAnn example += "x"
}
Is anyone familiar with why this might occur?MerlinTHS
12/06/2023, 2:37 PMannotation class SomeAnn
fun foo(example: MutableList<String>) {
<!WRONG_ANNOTATION_TARGET!>@SomeAnn<!> example += "x"
}
MerlinTHS
12/06/2023, 2:52 PM@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.EXPRESSION)
annotation class SomeAnn
fun foo(example: MutableList<String>) {
@SomeAnn example += "x"
}
But the FIR is actually pretty different. If the annotation is above your expression, it has nothing to do with it.
@R|kotlin/annotation/Retention|(value = Q|kotlin/annotation/AnnotationRetention|.R|kotlin/annotation/AnnotationRetention.SOURCE|) @R|kotlin/annotation/Target|(allowedTargets = vararg(Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.LOCAL_VARIABLE|, Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.FIELD|, Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.EXPRESSION|)) public final annotation class SomeAnn : R|kotlin/Annotation| {
public constructor(): R|SomeAnn| {
super<R|kotlin/Any|>()
}
}
public final fun foo(example: R|kotlin/collections/MutableList<kotlin/String>|): R|kotlin/Unit| {
R|<local>/example|.R|kotlin/collections/plusAssign|<R|kotlin/String|>(String(x))
}
Whereas when it is in the same line:
@R|kotlin/annotation/Retention|(value = Q|kotlin/annotation/AnnotationRetention|.R|kotlin/annotation/AnnotationRetention.SOURCE|) @R|kotlin/annotation/Target|(allowedTargets = vararg(Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.EXPRESSION|)) public final annotation class SomeAnn : R|kotlin/Annotation| {
public constructor(): R|SomeAnn| {
super<R|kotlin/Any|>()
}
}
public final fun foo(example: R|kotlin/collections/MutableList<kotlin/String>|): R|kotlin/Unit| {
@R|SomeAnn|() R|<local>/example|.R|kotlin/collections/plusAssign|<R|kotlin/String|>(String(x))
}
MerlinTHS
12/06/2023, 3:00 PMstatement
: (label | annotation)* ( declaration | assignment | loopStatement | expression)
;
https://github.com/Kotlin/kotlin-spec/blob/release/grammar/src/main/antlr/KotlinParser.g4
But since a newline is allowed, it looks like a bug.
annotation
: (singleAnnotation | multiAnnotation) NL*
;
Maybe it's because of the non-matching annotation target?
But a warning like "useless annotation" would be great.Tracey Yoshima
12/06/2023, 5:16 PMEXPRESSION
as a target.
The annotation will exist on the FIR on other expressions where a new line exists between the annotation and expression. But seems to disappear in this case.MerlinTHS
12/06/2023, 5:23 PMTracey Yoshima
12/06/2023, 5:33 PMMerlinTHS
12/07/2023, 1:22 PM@Ann example += "test"
as (@Ann example) += "test"
where the explicit receiver (example) is the annotation target,
whereas @Ann\n example += "test"
is interpreted as @Ann() (example += "test")
and targets the statement.
Both variants that annotate the statement will miss the annotation in the FIR.
You can find the operator call transformations in FirExpressionResolveTransformer.transformAssignmentOperatorStatement
.
Expressions using +=
with primitives like
var i = 0
@SomeAnn
i += 6
include the annotations of the original assignmentOperatorStatement
.
They are transformed by the following snippet (in the local function chooseOperator
):
buildVariableAssignment {
source = assignmentOperatorStatement.source
lValue = assignmentLeftArgument
rValue = resolvedOperatorCall
annotations += assignmentOperatorStatement.annotations
}
Calls to plusAssign
miss the annotations in the transformation process.
val assignOperatorCall = generator.createAssignOperatorCall()
val resolvedAssignCall = resolveCandidateForAssignmentOperatorCall {
assignOperatorCall.transformSingle(this, ResolutionMode.ContextDependent)
}
val assignCallReference = resolvedAssignCall.calleeReference as? FirNamedReferenceWithCandidate
val assignIsSuccessful = assignCallReference?.isError == false
...
fun chooseAssign(): FirStatement {
callCompleter.completeCall(resolvedAssignCall, ResolutionMode.ContextIndependent)
dataFlowAnalyzer.exitFunctionCall(resolvedAssignCall, callCompleted = true)
return resolvedAssignCall
}
I would expected the last code snipped to also include the annotations from the original statements,
like it's done in chooseOperator
.MerlinTHS
12/08/2023, 3:27 PMTracey Yoshima
12/08/2023, 5:21 PM1.9.20
!