Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KE2: extract binary operators #17761

Merged
merged 6 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 0 additions & 149 deletions java/kotlin-extractor2/src/main/kotlin/KotlinFileExtractor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2979,93 +2979,6 @@ OLD: KE1

val dr = c.dispatchReceiver
when {
isNumericFunction(
target,
"plus",
"minus",
"times",
"div",
"rem",
"and",
"or",
"xor",
"shl",
"shr",
"ushr"
) -> {
val type = useType(c.type)
val id: Label<out DbExpr> =
when (val targetName = target.name.asString()) {
"minus" -> {
val id = tw.getFreshIdLabel<DbSubexpr>()
tw.writeExprs_subexpr(id, type.javaResult.id, parent, idx)
id
}
"times" -> {
val id = tw.getFreshIdLabel<DbMulexpr>()
tw.writeExprs_mulexpr(id, type.javaResult.id, parent, idx)
id
}
"div" -> {
val id = tw.getFreshIdLabel<DbDivexpr>()
tw.writeExprs_divexpr(id, type.javaResult.id, parent, idx)
id
}
"rem" -> {
val id = tw.getFreshIdLabel<DbRemexpr>()
tw.writeExprs_remexpr(id, type.javaResult.id, parent, idx)
id
}
"and" -> {
val id = tw.getFreshIdLabel<DbAndbitexpr>()
tw.writeExprs_andbitexpr(id, type.javaResult.id, parent, idx)
id
}
"or" -> {
val id = tw.getFreshIdLabel<DbOrbitexpr>()
tw.writeExprs_orbitexpr(id, type.javaResult.id, parent, idx)
id
}
"xor" -> {
val id = tw.getFreshIdLabel<DbXorbitexpr>()
tw.writeExprs_xorbitexpr(id, type.javaResult.id, parent, idx)
id
}
"shl" -> {
val id = tw.getFreshIdLabel<DbLshiftexpr>()
tw.writeExprs_lshiftexpr(id, type.javaResult.id, parent, idx)
id
}
"shr" -> {
val id = tw.getFreshIdLabel<DbRshiftexpr>()
tw.writeExprs_rshiftexpr(id, type.javaResult.id, parent, idx)
id
}
"ushr" -> {
val id = tw.getFreshIdLabel<DbUrshiftexpr>()
tw.writeExprs_urshiftexpr(id, type.javaResult.id, parent, idx)
id
}
else -> {
logger.errorElement("Unhandled binary target name: $targetName", c)
return
}
}
tw.writeExprsKotlinType(id, type.kotlinResult.id)
if (
isFunction(
target,
"kotlin",
"Byte or Short",
{ it == "Byte" || it == "Short" },
"and",
"or",
"xor"
)
)
binopExt(id)
else binopDisp(id)
}
// != gets desugared into not and ==. Here we resugar it.
c.origin == IrStatementOrigin.EXCLEQ &&
isFunction(target, "kotlin", "Boolean", "not") &&
Expand All @@ -3079,18 +2992,6 @@ OLD: KE1
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, dr, callable, enclosingStmt)
}
c.origin == IrStatementOrigin.EXCLEQEQ &&
isFunction(target, "kotlin", "Boolean", "not") &&
c.valueArgumentsCount == 0 &&
dr != null &&
dr is IrCall &&
isBuiltinCallInternal(dr, "EQEQEQ") -> {
val id = tw.getFreshIdLabel<DbNeexpr>()
val type = useType(c.type)
tw.writeExprs_neexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, dr, callable, enclosingStmt)
}
c.origin == IrStatementOrigin.EXCLEQ &&
isFunction(target, "kotlin", "Boolean", "not") &&
c.valueArgumentsCount == 0 &&
Expand Down Expand Up @@ -3150,46 +3051,6 @@ OLD: KE1
// We need to handle all the builtin operators defines in BuiltInOperatorNames in
// compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/IrBuiltIns.kt
// as they can't be extracted as external dependencies.
isBuiltinCallInternal(c, "less") -> {
if (c.origin != IrStatementOrigin.LT) {
logger.warnElement("Unexpected origin for LT: ${c.origin}", c)
}
val id = tw.getFreshIdLabel<DbLtexpr>()
val type = useType(c.type)
tw.writeExprs_ltexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "lessOrEqual") -> {
if (c.origin != IrStatementOrigin.LTEQ) {
logger.warnElement("Unexpected origin for LTEQ: ${c.origin}", c)
}
val id = tw.getFreshIdLabel<DbLeexpr>()
val type = useType(c.type)
tw.writeExprs_leexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "greater") -> {
if (c.origin != IrStatementOrigin.GT) {
logger.warnElement("Unexpected origin for GT: ${c.origin}", c)
}
val id = tw.getFreshIdLabel<DbGtexpr>()
val type = useType(c.type)
tw.writeExprs_gtexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "greaterOrEqual") -> {
if (c.origin != IrStatementOrigin.GTEQ) {
logger.warnElement("Unexpected origin for GTEQ: ${c.origin}", c)
}
val id = tw.getFreshIdLabel<DbGeexpr>()
val type = useType(c.type)
tw.writeExprs_geexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "EQEQ") -> {
if (c.origin != IrStatementOrigin.EQEQ) {
logger.warnElement("Unexpected origin for EQEQ: ${c.origin}", c)
Expand All @@ -3200,16 +3061,6 @@ OLD: KE1
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "EQEQEQ") -> {
if (c.origin != IrStatementOrigin.EQEQEQ) {
logger.warnElement("Unexpected origin for EQEQEQ: ${c.origin}", c)
}
val id = tw.getFreshIdLabel<DbEqexpr>()
val type = useType(c.type)
tw.writeExprs_eqexpr(id, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)
binOp(id, c, callable, enclosingStmt)
}
isBuiltinCallInternal(c, "ieee754equals") -> {
if (c.origin != IrStatementOrigin.EQEQ) {
logger.warnElement("Unexpected origin for ieee754equals: ${c.origin}", c)
Expand Down
114 changes: 81 additions & 33 deletions java/kotlin-extractor2/src/main/kotlin/entities/Expression.kt
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,14 @@ private fun KaFunctionSymbol.hasName(
)
}

private fun KaFunctionSymbol.isNumericWithName(functionName: String): Boolean {
return this.hasName("kotlin", "Int", functionName) ||
this.hasName("kotlin", "Byte", functionName) ||
this.hasName("kotlin", "Short", functionName) ||
this.hasName("kotlin", "Long", functionName) ||
this.hasName("kotlin", "Float", functionName) ||
this.hasName("kotlin", "Double", functionName)
private fun KaFunctionSymbol?.isNumericWithName(functionName: String): Boolean {
return this != null &&
(this.hasName("kotlin", "Int", functionName) ||
this.hasName("kotlin", "Byte", functionName) ||
this.hasName("kotlin", "Short", functionName) ||
this.hasName("kotlin", "Long", functionName) ||
this.hasName("kotlin", "Float", functionName) ||
this.hasName("kotlin", "Double", functionName))
}

context(KaSession)
Expand Down Expand Up @@ -297,37 +298,84 @@ private fun KotlinFileExtractor.extractBinaryExpression(
val op = expression.operationToken
val target = expression.resolveCallTarget()?.symbol
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the plan for target == null?

If we're just going to emit an errorExpr, and perhaps add some more info in a supplementary table, then can we handle that case first, and make isNumericWithName and isBinaryPlus take a non-null KaFunctionSymbol and not have to handle the null case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are cases below where target is null, for example in case of ===.


when (op) {
KtTokens.PLUS -> {
if (target == null) {
TODO()
}

if (target.isNumericWithName("plus") ||
target.hasName("kotlin", "String", "plus") ||
target.hasMatchingNames(
CallableId(FqName("kotlin"), null, Name.identifier("plus")),
ClassId(FqName("kotlin"), Name.identifier("String")),
nullability = KaTypeNullability.NULLABLE,
)
) {
val id = tw.getFreshIdLabel<DbAddexpr>()
val type = useType(expression.expressionType)
val exprParent = parent.expr(expression, callable)
tw.writeExprs_addexpr(id, type.javaResult.id, exprParent.parent, exprParent.idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)

extractExprContext(id, tw.getLocation(expression), callable, exprParent.enclosingStmt)
extractExpressionExpr(expression.left!!, callable, id, 0, exprParent.enclosingStmt)
extractExpressionExpr(expression.right!!, callable, id, 1, exprParent.enclosingStmt)
} else {
TODO("Extract as method call")
if (op == KtTokens.PLUS && target.isBinaryPlus()) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_addexpr)
} else if (op == KtTokens.MINUS && target.isNumericWithName("minus")) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_subexpr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far at least this is very regular; I wonder if it would be worth writing as

    val writeExprs_fun = if (op == KtTokens.PLUS && target.isBinaryPlus()) { tw::writeExprs_addexpr }
                         else if ...
                         else null
    if (writeExprs_fun != null) {
        extractBinaryExpression(expression, callable, parent, writeExprs_fun
    } else {
        something_else
    }

?

And either way, something like

when {
    op == KtTokens.PLUS && target.isBinaryPlus() -> tw::writeExprs_addexpr // or -> extractBinaryExpression(expression, callable, parent, tw::writeExprs_addexpr)
    ...
    else -> null
}

might be nicer than a big if/elseif

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer merging this PR, #17939, and #17874 first. I can do a followup refactoring afterwards.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the followup refactoring PR: #17974

} else if (op == KtTokens.MUL && target.isNumericWithName("times")) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_mulexpr)
} else if (op == KtTokens.DIV && target.isNumericWithName("div")) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_divexpr)
} else if (op == KtTokens.PERC && target.isNumericWithName("rem")) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_remexpr)
} else if (op == KtTokens.IDENTIFIER && target.isNumericWithName("and")) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_andbitexpr)
} else if (op == KtTokens.IDENTIFIER && target.isNumericWithName("or")) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_orbitexpr)
} else if (op == KtTokens.IDENTIFIER && target.isNumericWithName("xor")) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_xorbitexpr)
} else if (op == KtTokens.IDENTIFIER && target.isNumericWithName("shl")) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_lshiftexpr)
} else if (op == KtTokens.IDENTIFIER && target.isNumericWithName("shr")) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_rshiftexpr)
} else if (op == KtTokens.IDENTIFIER && target.isNumericWithName("ushr")) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_urshiftexpr)
} else if (op == KtTokens.EQEQEQ && target == null) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_eqexpr)
} else if (op == KtTokens.EXCLEQEQEQ && target == null) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_neexpr)
} else if (op in listOf(KtTokens.LT, KtTokens.GT, KtTokens.LTEQ, KtTokens.GTEQ)) {
if (target.isNumericWithName("compareTo")) {
when (op) {
KtTokens.LT -> extractBinaryExpression(expression, callable, parent, tw::writeExprs_ltexpr)
KtTokens.GT -> extractBinaryExpression(expression, callable, parent, tw::writeExprs_gtexpr)
KtTokens.LTEQ -> extractBinaryExpression(expression, callable, parent, tw::writeExprs_leexpr)
KtTokens.GTEQ -> extractBinaryExpression(expression, callable, parent, tw::writeExprs_geexpr)
else -> TODO("error")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're changing this to just return tw::writeExprs_ltexpr as above, then we could drop the op in listOf check and just return null in the else case.

}
} else {
TODO("Extract lowered equivalent call, such as `a.compareTo(b) < 0`")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, but my previous suggestion won't work if we need to do something special in this case.
I think we should add a comment explaining what's going on with the AST in that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the comment. Instead we should merge this too: #17939

}

else -> TODO()
} else {
// todo: other operators, such as .., ..<, in, !in, =, +=, -=, *=, /=, %=, ==, !=,
TODO("Extract as method call")
}
}

private fun KaFunctionSymbol?.isBinaryPlus(): Boolean {
return this != null && (
this.isNumericWithName("plus") ||
this.hasName("kotlin", "String", "plus") ||
/* The target for `(null as String?) + null` is `public operator fun String?.plus(other: Any?): String` */
this.hasMatchingNames(
CallableId(FqName("kotlin"), null, Name.identifier("plus")),
ClassId(FqName("kotlin"), Name.identifier("String")),
nullability = KaTypeNullability.NULLABLE,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be worth adding a comment saying what this last case corresponds to

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can dig that up, but this bit was just refactored. It was originally introduced in #17752

Copy link
Contributor Author

@tamasvajk tamasvajk Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment added in 53460d7.

))
}

context(KaSession)
private fun <T : DbBinaryexpr> KotlinFileExtractor.extractBinaryExpression(
expression: KtBinaryExpression,
callable: Label<out DbCallable>,
parent: StmtExprParent,
extractExpression: (
id: Label<out T>,
typeid: Label<out DbType>,
parent: Label<out DbExprparent>,
idx: Int
) -> Unit
) {
val id = tw.getFreshIdLabel<T>()
val type = useType(expression.expressionType)
val exprParent = parent.expr(expression, callable)
extractExpression(id, type.javaResult.id, exprParent.parent, exprParent.idx)
tw.writeExprsKotlinType(id, type.kotlinResult.id)

extractExprContext(id, tw.getLocation(expression), callable, exprParent.enclosingStmt)
extractExpressionExpr(expression.left!!, callable, id, 0, exprParent.enclosingStmt)
extractExpressionExpr(expression.right!!, callable, id, 1, exprParent.enclosingStmt)
}

context(KaSession)
Expand Down
Loading