Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 377bd8f

Browse files
smowtonigfoo
authored andcommitted
Extract String?.plus as either an AddExpr or a call to an intrinsic
If it is used by the compiler to implement the infix plus operator, resugar it and extract a `+` as Java would. If it is literally called by the user (e.g. `(if (x) then "not null" else null).plus(something)`), then extract a call to the real method Intrinsics.stringPlus (a two-arg static method).
1 parent 93e8d5a commit 377bd8f

4 files changed

Lines changed: 152 additions & 105 deletions

File tree

java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt

Lines changed: 142 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -999,9 +999,112 @@ open class KotlinFileExtractor(
999999
}
10001000
}
10011001

1002+
fun extractRawMethodAccess(
1003+
syntacticCallTarget: IrFunction,
1004+
callsite: IrCall,
1005+
enclosingCallable: Label<out DbCallable>,
1006+
callsiteParent: Label<out DbExprparent>,
1007+
childIdx: Int,
1008+
enclosingStmt: Label<out DbStmt>,
1009+
valueArguments: List<IrExpression?>,
1010+
dispatchReceiver: IrExpression?,
1011+
extensionReceiver: IrExpression?,
1012+
typeArguments: List<IrType> = listOf(),
1013+
extractClassTypeArguments: Boolean = false) {
1014+
1015+
val callTarget = syntacticCallTarget.target
1016+
val id = tw.getFreshIdLabel<DbMethodaccess>()
1017+
val type = useType(callsite.type)
1018+
val locId = tw.getLocation(callsite)
1019+
tw.writeExprs_methodaccess(id, type.javaResult.id, callsiteParent, childIdx)
1020+
tw.writeExprsKotlinType(id, type.kotlinResult.id)
1021+
tw.writeHasLocation(id, locId)
1022+
tw.writeCallableEnclosingExpr(id, enclosingCallable)
1023+
tw.writeStatementEnclosingExpr(id, enclosingStmt)
1024+
1025+
// type arguments at index -2, -3, ...
1026+
extractTypeArguments(typeArguments, callsite, id, enclosingCallable, enclosingStmt, -2, true)
1027+
1028+
if (callTarget.isLocalFunction()) {
1029+
val ids = getLocallyVisibleFunctionLabels(callTarget)
1030+
1031+
val methodId = ids.function
1032+
tw.writeCallableBinding(id, methodId)
1033+
1034+
val idNewexpr = tw.getFreshIdLabel<DbNewexpr>()
1035+
tw.writeExprs_newexpr(idNewexpr, ids.type.javaResult.id, id, -1)
1036+
tw.writeExprsKotlinType(idNewexpr, ids.type.kotlinResult.id)
1037+
tw.writeHasLocation(idNewexpr, locId)
1038+
tw.writeCallableEnclosingExpr(idNewexpr, enclosingCallable)
1039+
tw.writeStatementEnclosingExpr(idNewexpr, enclosingStmt)
1040+
tw.writeCallableBinding(idNewexpr, ids.constructor)
1041+
1042+
@Suppress("UNCHECKED_CAST")
1043+
tw.writeIsAnonymClass(ids.type.javaResult.id as Label<DbClass>, idNewexpr)
1044+
1045+
extractTypeAccess(pluginContext.irBuiltIns.anyType, enclosingCallable, idNewexpr, -3, callsite, enclosingStmt)
1046+
} else {
1047+
// Returns true if type is C<T1, T2, ...> where C is declared `class C<T1, T2, ...> { ... }`
1048+
fun isUnspecialised(type: IrSimpleType) =
1049+
type.classifier.owner is IrClass &&
1050+
(type.classifier.owner as IrClass).typeParameters.zip(type.arguments).all { paramAndArg ->
1051+
(paramAndArg.second as? IrTypeProjection)?.let {
1052+
// Type arg refers to the class' own type parameter?
1053+
it.variance == Variance.INVARIANT &&
1054+
it.type.classifierOrNull?.owner === paramAndArg.first
1055+
} ?: false
1056+
}
1057+
1058+
val drType = dispatchReceiver?.type
1059+
val methodId =
1060+
if (drType != null && extractClassTypeArguments && drType is IrSimpleType && !isUnspecialised(drType))
1061+
useFunction<DbCallable>(callTarget, getDeclaringTypeArguments(callTarget, drType))
1062+
else
1063+
useFunction<DbCallable>(callTarget)
1064+
1065+
tw.writeCallableBinding(id, methodId)
1066+
1067+
if (dispatchReceiver != null) {
1068+
extractExpressionExpr(dispatchReceiver, enclosingCallable, id, -1, enclosingStmt)
1069+
} else if(callTarget.isStaticMethodOfClass) {
1070+
extractTypeAccess(callTarget.parentAsClass.toRawType(), enclosingCallable, id, -1, callsite, enclosingStmt)
1071+
}
1072+
}
1073+
1074+
val idxOffset: Int
1075+
if (extensionReceiver != null) {
1076+
extractExpressionExpr(extensionReceiver, enclosingCallable, id, 0, enclosingStmt)
1077+
idxOffset = 1
1078+
} else {
1079+
idxOffset = 0
1080+
}
1081+
1082+
valueArguments.forEachIndexed { i, arg ->
1083+
if(arg != null) {
1084+
extractExpressionExpr(arg, enclosingCallable, id, i + idxOffset, enclosingStmt)
1085+
}
1086+
}
1087+
}
1088+
1089+
fun findFunction(cls: IrClass, name: String): IrFunction? = cls.declarations.find { it is IrFunction && it.name.asString() == name } as IrFunction?
1090+
1091+
val jvmIntrinsicsClass by lazy {
1092+
val result = pluginContext.referenceClass(FqName("kotlin.jvm.internal.Intrinsics"))?.owner
1093+
result?.let { extractExternalClassLater(it) }
1094+
result
1095+
}
1096+
1097+
fun findJdkIntrinsicOrWarn(name: String, warnAgainstElement: IrElement): IrFunction? {
1098+
val result = jvmIntrinsicsClass?.let { findFunction(it, name) }
1099+
if(result == null) {
1100+
logger.warnElement(Severity.ErrorSevere, "Couldn't find JVM intrinsic function $name", warnAgainstElement)
1101+
}
1102+
return result
1103+
}
1104+
10021105
fun extractCall(c: IrCall, callable: Label<out DbCallable>, parent: Label<out DbExprparent>, idx: Int, enclosingStmt: Label<out DbStmt>) {
10031106
with("call", c) {
1004-
fun isFunction(pkgName: String, className: String, fName: String, hasQuestionMark: Boolean = false): Boolean {
1107+
fun isFunction(pkgName: String, className: String, fName: String, hasQuestionMark: Boolean? = false): Boolean {
10051108
val verbose = false
10061109
fun verboseln(s: String) { if(verbose) println(s) }
10071110
verboseln("Attempting match for $pkgName $className $fName")
@@ -1012,10 +1115,14 @@ open class KotlinFileExtractor(
10121115
}
10131116
val extensionReceiverParameter = target.extensionReceiverParameter
10141117
val targetClass = if (extensionReceiverParameter == null) {
1118+
if (hasQuestionMark == true) {
1119+
verboseln("Nullablility of type didn't match (target is not an extension method)")
1120+
return false
1121+
}
10151122
target.parent
10161123
} else {
10171124
val st = extensionReceiverParameter.type as? IrSimpleType
1018-
if (st?.hasQuestionMark != hasQuestionMark) {
1125+
if (hasQuestionMark != null && st?.hasQuestionMark != hasQuestionMark) {
10191126
verboseln("Nullablility of type didn't match")
10201127
return false
10211128
}
@@ -1050,86 +1157,15 @@ open class KotlinFileExtractor(
10501157
isFunction("kotlin", "Float", fName) ||
10511158
isFunction("kotlin", "Double", fName)
10521159
}
1053-
1160+
10541161
fun extractMethodAccess(syntacticCallTarget: IrFunction, extractMethodTypeArguments: Boolean = true, extractClassTypeArguments: Boolean = false) {
1055-
val callTarget = syntacticCallTarget.target
1056-
val id = tw.getFreshIdLabel<DbMethodaccess>()
1057-
val type = useType(c.type)
1058-
val locId = tw.getLocation(c)
1059-
tw.writeExprs_methodaccess(id, type.javaResult.id, parent, idx)
1060-
tw.writeExprsKotlinType(id, type.kotlinResult.id)
1061-
tw.writeHasLocation(id, locId)
1062-
tw.writeCallableEnclosingExpr(id, callable)
1063-
tw.writeStatementEnclosingExpr(id, enclosingStmt)
1064-
1065-
if (extractMethodTypeArguments) {
1066-
// type arguments at index -2, -3, ...
1067-
extractTypeArguments(c, id, callable, enclosingStmt, -2, true)
1068-
}
1069-
1070-
if (callTarget.isLocalFunction()) {
1071-
val ids = getLocallyVisibleFunctionLabels(callTarget)
1072-
1073-
val methodId = ids.function
1074-
tw.writeCallableBinding(id, methodId)
1075-
1076-
val idNewexpr = tw.getFreshIdLabel<DbNewexpr>()
1077-
tw.writeExprs_newexpr(idNewexpr, ids.type.javaResult.id, id, -1)
1078-
tw.writeExprsKotlinType(idNewexpr, ids.type.kotlinResult.id)
1079-
tw.writeHasLocation(idNewexpr, locId)
1080-
tw.writeCallableEnclosingExpr(idNewexpr, callable)
1081-
tw.writeStatementEnclosingExpr(idNewexpr, enclosingStmt)
1082-
tw.writeCallableBinding(idNewexpr, ids.constructor)
1083-
1084-
@Suppress("UNCHECKED_CAST")
1085-
tw.writeIsAnonymClass(ids.type.javaResult.id as Label<DbClass>, idNewexpr)
1086-
1087-
extractTypeAccess(pluginContext.irBuiltIns.anyType, callable, idNewexpr, -3, c, enclosingStmt)
1088-
} else {
1089-
val dr = c.dispatchReceiver
1090-
1091-
// Returns true if type is C<T1, T2, ...> where C is declared `class C<T1, T2, ...> { ... }`
1092-
fun isUnspecialised(type: IrSimpleType) =
1093-
type.classifier.owner is IrClass &&
1094-
(type.classifier.owner as IrClass).typeParameters.zip(type.arguments).all { paramAndArg ->
1095-
(paramAndArg.second as? IrTypeProjection)?.let {
1096-
// Type arg refers to the class' own type parameter?
1097-
it.variance == Variance.INVARIANT &&
1098-
it.type.classifierOrNull?.owner === paramAndArg.first
1099-
} ?: false
1100-
}
1101-
1102-
val drType = dr?.type
1103-
val methodId =
1104-
if (drType != null && extractClassTypeArguments && drType is IrSimpleType && !isUnspecialised(drType))
1105-
useFunction<DbCallable>(callTarget, getDeclaringTypeArguments(callTarget, drType))
1106-
else
1107-
useFunction<DbCallable>(callTarget)
1108-
1109-
tw.writeCallableBinding(id, methodId)
1110-
1111-
if (dr != null) {
1112-
extractExpressionExpr(dr, callable, id, -1, enclosingStmt)
1113-
} else if(callTarget.isStaticMethodOfClass) {
1114-
extractTypeAccess(callTarget.parentAsClass.toRawType(), callable, id, -1, c, enclosingStmt)
1115-
}
1116-
}
1117-
1118-
val er = c.extensionReceiver
1119-
val idxOffset: Int
1120-
if (er != null) {
1121-
extractExpressionExpr(er, callable, id, 0, enclosingStmt)
1122-
idxOffset = 1
1123-
} else {
1124-
idxOffset = 0
1125-
}
1126-
1127-
for(i in 0 until c.valueArgumentsCount) {
1128-
val arg = c.getValueArgument(i)
1129-
if(arg != null) {
1130-
extractExpressionExpr(arg, callable, id, i + idxOffset, enclosingStmt)
1131-
}
1132-
}
1162+
val typeArgs =
1163+
if (extractMethodTypeArguments)
1164+
(0 until c.typeArgumentsCount).map { c.getTypeArgument(it)!! }
1165+
else
1166+
listOf()
1167+
1168+
extractRawMethodAccess(syntacticCallTarget, c, callable, parent, idx, enclosingStmt, (0 until c.valueArgumentsCount).map { c.getValueArgument(it) }, c.dispatchReceiver, c.extensionReceiver, typeArgs, extractClassTypeArguments)
11331169
}
11341170

11351171
fun extractSpecialEnumFunction(fnName: String){
@@ -1153,11 +1189,11 @@ open class KotlinFileExtractor(
11531189
tw.writeCallableEnclosingExpr(id, callable)
11541190
tw.writeStatementEnclosingExpr(id, enclosingStmt)
11551191

1156-
val dr = c.dispatchReceiver
1157-
if(dr == null) {
1158-
logger.warnElement(Severity.ErrorSevere, "Dispatch receiver not found", c)
1192+
val receiver = c.dispatchReceiver ?: c.extensionReceiver
1193+
if(receiver == null) {
1194+
logger.warnElement(Severity.ErrorSevere, "Receiver not found", c)
11591195
} else {
1160-
extractExpressionExpr(dr, callable, id, 0, enclosingStmt)
1196+
extractExpressionExpr(receiver, callable, id, 0, enclosingStmt)
11611197
}
11621198
if(c.valueArgumentsCount < 1) {
11631199
logger.warnElement(Severity.ErrorSevere, "No RHS found", c)
@@ -1178,21 +1214,17 @@ open class KotlinFileExtractor(
11781214
when {
11791215
c.origin == IrStatementOrigin.PLUS &&
11801216
(isNumericFunction("plus")
1181-
|| isFunction("kotlin", "String", "plus")) -> {
1217+
|| isFunction("kotlin", "String", "plus", null)) -> {
11821218
val id = tw.getFreshIdLabel<DbAddexpr>()
11831219
val type = useType(c.type)
11841220
tw.writeExprs_addexpr(id, type.javaResult.id, parent, idx)
11851221
tw.writeExprsKotlinType(id, type.kotlinResult.id)
11861222
binopDisp(id)
11871223
}
11881224
isFunction("kotlin", "String", "plus", true) -> {
1189-
// TODO: this is not correct. `a + b` becomes `(a?:"\"null\"") + (b?:"\"null\"")`.
1190-
val func = pluginContext.irBuiltIns.stringType.classOrNull?.owner?.declarations?.find { it is IrFunction && it.name.asString() == "plus" }
1191-
if (func == null) {
1192-
logger.warnElement(Severity.ErrorSevere, "Couldn't find plus function on string type", c)
1193-
return
1225+
findJdkIntrinsicOrWarn("stringPlus", c)?.let { stringPlusFn ->
1226+
extractRawMethodAccess(stringPlusFn, c, callable, parent, idx, enclosingStmt, listOf(c.extensionReceiver, c.getValueArgument(0)), null, null)
11941227
}
1195-
extractMethodAccess(func as IrFunction)
11961228
}
11971229
c.origin == IrStatementOrigin.MINUS && isNumericFunction("minus") -> {
11981230
val id = tw.getFreshIdLabel<DbSubexpr>()
@@ -1475,21 +1507,32 @@ open class KotlinFileExtractor(
14751507
}
14761508
}
14771509

1478-
private fun <T : IrSymbol> extractTypeArguments(
1479-
c: IrMemberAccessExpression<T>,
1480-
id: Label<out DbExprparent>,
1481-
callable: Label<out DbCallable>,
1510+
private fun extractTypeArguments(
1511+
typeArgs: List<IrType>,
1512+
elementForLocation: IrElement,
1513+
parentExpr: Label<out DbExprparent>,
1514+
enclosingCallable: Label<out DbCallable>,
14821515
enclosingStmt: Label<out DbStmt>,
14831516
startIndex: Int = 0,
14841517
reverse: Boolean = false
14851518
) {
1486-
for (argIdx in 0 until c.typeArgumentsCount) {
1487-
val arg = c.getTypeArgument(argIdx)!!
1519+
typeArgs.forEachIndexed { argIdx, arg ->
14881520
val mul = if (reverse) -1 else 1
1489-
extractTypeAccess(arg, callable, id, argIdx * mul + startIndex, c, enclosingStmt, TypeContext.GENERIC_ARGUMENT)
1521+
extractTypeAccess(arg, enclosingCallable, parentExpr, argIdx * mul + startIndex, elementForLocation, enclosingStmt, TypeContext.GENERIC_ARGUMENT)
14901522
}
14911523
}
14921524

1525+
private fun <T : IrSymbol> extractTypeArguments(
1526+
c: IrMemberAccessExpression<T>,
1527+
parentExpr: Label<out DbExprparent>,
1528+
enclosingCallable: Label<out DbCallable>,
1529+
enclosingStmt: Label<out DbStmt>,
1530+
startIndex: Int = 0,
1531+
reverse: Boolean = false
1532+
) {
1533+
extractTypeArguments((0 until c.typeArgumentsCount).map { c.getTypeArgument(it)!! }, c, parentExpr, enclosingCallable, enclosingStmt, startIndex, reverse)
1534+
}
1535+
14931536
private fun extractConstructorCall(
14941537
e: IrFunctionAccessExpression,
14951538
parent: Label<out DbExprparent>,

java/ql/test/kotlin/library-tests/exprs/PrintAst.expected

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ exprs.kt:
582582
# 127| 0: [VarAccess] str1
583583
# 127| 1: [VarAccess] str2
584584
# 127| 2: [StringLiteral] bar
585-
# 127| 3: [MethodAccess] plus(...)
585+
# 127| 3: [AddExpr] ... + ...
586586
# 127| 0: [VarAccess] str2
587587
# 127| 1: [VarAccess] str1
588588
# 127| 4: [StringLiteral] baz
@@ -873,12 +873,13 @@ exprs.kt:
873873
# 202| 0: [VarAccess] aa
874874
# 203| 1: [LocalVariableDeclStmt] var ...;
875875
# 203| 1: [LocalVariableDeclExpr] b0
876-
# 203| 0: [MethodAccess] plus(...)
876+
# 203| 0: [MethodAccess] stringPlus(...)
877+
# 203| -1: [TypeAccess] Intrinsics
877878
# 203| 0: [VarAccess] s
878879
# 203| 1: [IntegerLiteral] 5
879880
# 204| 2: [LocalVariableDeclStmt] var ...;
880881
# 204| 1: [LocalVariableDeclExpr] b1
881-
# 204| 0: [MethodAccess] plus(...)
882+
# 204| 0: [AddExpr] ... + ...
882883
# 204| 0: [VarAccess] s
883884
# 204| 1: [IntegerLiteral] 5
884885
# 205| 3: [LocalVariableDeclStmt] var ...;

java/ql/test/kotlin/library-tests/exprs/binop.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,13 @@
7171
| exprs.kt:108:15:108:23 | ... == ... | exprs.kt:108:15:108:16 | fx | exprs.kt:108:22:108:23 | fy |
7272
| exprs.kt:109:15:109:23 | ... != ... | exprs.kt:109:15:109:16 | fx | exprs.kt:109:22:109:23 | fy |
7373
| exprs.kt:127:31:127:41 | ... + ... | exprs.kt:127:31:127:34 | str1 | exprs.kt:127:38:127:41 | str2 |
74+
| exprs.kt:127:50:127:60 | ... + ... | exprs.kt:127:50:127:53 | str2 | exprs.kt:127:57:127:60 | str1 |
7475
| exprs.kt:128:16:128:26 | ... + ... | exprs.kt:128:16:128:19 | str1 | exprs.kt:128:23:128:26 | str2 |
7576
| exprs.kt:131:12:131:23 | ... > ... | exprs.kt:131:12:131:19 | variable | exprs.kt:131:23:131:23 | 0 |
7677
| exprs.kt:135:12:135:20 | ... + ... | exprs.kt:135:12:135:14 | 123 | exprs.kt:135:18:135:20 | 456 |
7778
| exprs.kt:161:8:161:16 | ... != ... | exprs.kt:161:8:161:8 | r | exprs.kt:161:13:161:16 | null |
7879
| exprs.kt:190:31:190:37 | ... + ... | exprs.kt:190:31:190:32 | getA1(...) | exprs.kt:190:36:190:37 | a2 |
80+
| exprs.kt:204:19:204:23 | ... + ... | exprs.kt:204:19:204:19 | s | exprs.kt:204:23:204:23 | 5 |
7981
| exprs.kt:206:19:206:25 | ... + ... | exprs.kt:206:20:206:21 | ...!! | exprs.kt:206:25:206:25 | 5 |
8082
| funcExprs.kt:26:35:26:42 | ... + ... | funcExprs.kt:26:35:26:38 | this | funcExprs.kt:26:42:26:42 | a |
8183
| localFunctionCalls.kt:5:25:5:29 | ... + ... | localFunctionCalls.kt:5:25:5:25 | i | localFunctionCalls.kt:5:29:5:29 | x |

java/ql/test/kotlin/library-tests/exprs/exprs.expected

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@
447447
| exprs.kt:127:38:127:41 | str2 | exprs.kt:4:1:136:1 | topLevelMethod | VarAccess |
448448
| exprs.kt:127:43:127:47 | bar | exprs.kt:4:1:136:1 | topLevelMethod | StringLiteral |
449449
| exprs.kt:127:50:127:53 | str2 | exprs.kt:4:1:136:1 | topLevelMethod | VarAccess |
450-
| exprs.kt:127:50:127:60 | plus(...) | exprs.kt:4:1:136:1 | topLevelMethod | MethodAccess |
450+
| exprs.kt:127:50:127:60 | ... + ... | exprs.kt:4:1:136:1 | topLevelMethod | AddExpr |
451451
| exprs.kt:127:57:127:60 | str1 | exprs.kt:4:1:136:1 | topLevelMethod | VarAccess |
452452
| exprs.kt:127:62:127:65 | baz | exprs.kt:4:1:136:1 | topLevelMethod | StringLiteral |
453453
| exprs.kt:128:5:128:26 | str6 | exprs.kt:4:1:136:1 | topLevelMethod | LocalVariableDeclExpr |
@@ -582,11 +582,12 @@
582582
| exprs.kt:202:20:202:29 | toString(...) | exprs.kt:200:5:211:5 | x | MethodAccess |
583583
| exprs.kt:203:9:203:27 | b0 | exprs.kt:200:5:211:5 | x | LocalVariableDeclExpr |
584584
| exprs.kt:203:19:203:19 | s | exprs.kt:200:5:211:5 | x | VarAccess |
585-
| exprs.kt:203:21:203:27 | plus(...) | exprs.kt:200:5:211:5 | x | MethodAccess |
585+
| exprs.kt:203:21:203:27 | Intrinsics | exprs.kt:200:5:211:5 | x | TypeAccess |
586+
| exprs.kt:203:21:203:27 | stringPlus(...) | exprs.kt:200:5:211:5 | x | MethodAccess |
586587
| exprs.kt:203:26:203:26 | 5 | exprs.kt:200:5:211:5 | x | IntegerLiteral |
587588
| exprs.kt:204:9:204:23 | b1 | exprs.kt:200:5:211:5 | x | LocalVariableDeclExpr |
588589
| exprs.kt:204:19:204:19 | s | exprs.kt:200:5:211:5 | x | VarAccess |
589-
| exprs.kt:204:19:204:23 | plus(...) | exprs.kt:200:5:211:5 | x | MethodAccess |
590+
| exprs.kt:204:19:204:23 | ... + ... | exprs.kt:200:5:211:5 | x | AddExpr |
590591
| exprs.kt:204:23:204:23 | 5 | exprs.kt:200:5:211:5 | x | IntegerLiteral |
591592
| exprs.kt:205:9:205:29 | b2 | exprs.kt:200:5:211:5 | x | LocalVariableDeclExpr |
592593
| exprs.kt:205:19:205:19 | s | exprs.kt:200:5:211:5 | x | VarAccess |

0 commit comments

Comments
 (0)