Fold tuple-destructuring extract and write into one node

This commit is contained in:
Owen Mansel-Chan
2026-07-10 17:05:41 +01:00
parent d30162b2ca
commit c8f81bc1c4
3 changed files with 73 additions and 65 deletions

View File

@@ -506,17 +506,19 @@ module GoCfg {
// (see `skipCfg`): the per-case implicit variables are written at the
// case match nodes (see `IR::TypeSwitchImplicitVariableInstruction`),
// so the guard itself emits no assignment write node.
not n = any(Go::TypeSwitchStmt ts).getAssign()
not n = any(Go::TypeSwitchStmt ts).getAssign() and
// A tuple-destructuring assignment (`x, y = f()`) folds its per-target
// write into the `extract` node (see `IR::ExtractWriteInstruction`).
not extractNodeCondition(n, i)
or
// A `ValueSpec` without an initializer is written by its `zero-init`
// node directly (see `IR::InitVariableInstruction`), so only specs
// *with* an initializer emit an `assign` write node.
// node directly (see `IR::InitVariableInstruction`), and a
// tuple-destructuring declaration (`var x, y = f()`) is written by its
// `extract` node; only specs with a per-name initializer emit
// `assign:i`.
notBlankIdent(n.(Go::ValueSpec).getNameExpr(i)) and
exists(n.(Go::ValueSpec).getAnInit())
or
notBlankIdent(n.(Go::RangeElementExpr).getKey()) and i = 0
or
notBlankIdent(n.(Go::RangeElementExpr).getValue()) and i = 1
exists(n.(Go::ValueSpec).getAnInit()) and
not extractNodeCondition(n, i)
) and
tag = "assign:" + i.toString()
)
@@ -1105,16 +1107,17 @@ module GoCfg {
notBlankIdent(assgn.(Go::Assignment).getLhs(j)) and
// Compound assignments fold their write into `compound-rhs` (ord -1)
// above, so they emit no separate `assign:j` node.
not assgn instanceof Go::CompoundAssignStmt
not assgn instanceof Go::CompoundAssignStmt and
// Tuple-destructuring targets are written by their `extract` node.
not extractNodeCondition(assgn, j)
or
// A `ValueSpec` without an initializer is written by its `zero-init`
// node directly, so only specs *with* an initializer emit `assign:j`.
// node directly, and a tuple-destructuring declaration by its
// `extract` node, so only specs with a per-name initializer emit
// `assign:j`.
notBlankIdent(assgn.(Go::ValueSpec).getNameExpr(j)) and
exists(assgn.(Go::ValueSpec).getAnInit())
or
notBlankIdent(assgn.(Go::RangeElementExpr).getKey()) and j = 0
or
notBlankIdent(assgn.(Go::RangeElementExpr).getValue()) and j = 1
exists(assgn.(Go::ValueSpec).getAnInit()) and
not extractNodeCondition(assgn, j)
) and
result = "assign:" + j.toString() and
ord = 2 * j + 1

View File

@@ -646,8 +646,6 @@ module IR {
// specs *with* an initializer produce an `assign` node.
exists(assgn.(ValueSpec).getNameExpr(i)) and
exists(assgn.(ValueSpec).getAnInit())
or
assgn instanceof RangeElementExpr and i in [0, 1]
)
}
@@ -661,8 +659,6 @@ module IR {
spec.getNumName() = spec.getNumInit() and
result = evalExprInstruction(spec.getInit(i))
)
or
result.(ExtractTupleElementInstruction).isAdditional(assgn, "extract:" + i.toString())
}
override ControlFlow::Root getRoot() { result.isRootOf(assgn) }
@@ -776,6 +772,19 @@ module IR {
override ControlFlow::Root getRoot() { result.isRootOf(s) }
}
/**
* An `ExtractTupleElementInstruction` that also writes the extracted component
* to a left-hand side, as in a tuple-destructuring assignment (`x, y = f()`),
* a multi-variable declaration (`var x, y = f()`), or a `range` statement
* (`k, v := range m`).
*
* The extraction node performs the write directly, rather than feeding a
* separate `assign` node.
*/
class ExtractWriteInstruction extends WriteInstruction, ExtractTupleElementInstruction {
override Instruction getRhs() { result = this }
}
/**
* An instruction that computes the zero value to which a variable without an initializer
* expression is initialized.
@@ -1151,6 +1160,23 @@ module IR {
write.isAdditional(spec, "zero-init:" + idx.toString()) and
lhs = spec.getNameExpr(idx)
)
or
// A tuple-destructuring `extract` node writes its component directly (see
// `ExtractWriteInstruction`); blank-identifier targets are not written.
exists(AstNode assgn, int i | write.isAdditional(assgn, "extract:" + i.toString()) |
(
lhs = assgn.(Assignment).getLhs(i).stripParens()
or
lhs = assgn.(ValueSpec).getNameExpr(i)
or
exists(RangeElementExpr p | p = assgn |
i = 0 and lhs = p.getKey().stripParens()
or
i = 1 and lhs = p.getValue().stripParens()
)
) and
not lhs instanceof BlankIdent
)
} or
/** A composite literal element target. */
MkLiteralElementTarget(ControlFlow::Node write) {
@@ -1389,16 +1415,16 @@ module IR {
* Gets the instruction corresponding to the assignment of the key variable
* of range statement `rs`.
*/
AssignInstruction assignKeyInstruction(RangeStmt rs) {
result.isAdditional(rs.getPattern(), "assign:0")
ExtractWriteInstruction assignKeyInstruction(RangeStmt rs) {
result.isAdditional(rs.getPattern(), "extract:0")
}
/**
* Gets the instruction corresponding to the assignment of the value variable
* of range statement `rs`.
*/
AssignInstruction assignValueInstruction(RangeStmt rs) {
result.isAdditional(rs.getPattern(), "assign:1")
ExtractWriteInstruction assignValueInstruction(RangeStmt rs) {
result.isAdditional(rs.getPattern(), "extract:1")
}
/**

View File

@@ -1183,10 +1183,8 @@
| exprs.go:33:2:35:2 | if statement | exprs.go:33:5:33:24 | ... := ... |
| exprs.go:33:5:33:24 | ... := ... | exprs.go:33:14:33:24 | Before type assertion |
| exprs.go:33:5:33:24 | After ... := ... | exprs.go:33:27:33:28 | Before ok |
| exprs.go:33:5:33:24 | assign:0 ... := ... | exprs.go:33:5:33:24 | extract:1 ... := ... |
| exprs.go:33:5:33:24 | assign:1 ... := ... | exprs.go:33:5:33:24 | After ... := ... |
| exprs.go:33:5:33:24 | extract:0 ... := ... | exprs.go:33:5:33:24 | assign:0 ... := ... |
| exprs.go:33:5:33:24 | extract:1 ... := ... | exprs.go:33:5:33:24 | assign:1 ... := ... |
| exprs.go:33:5:33:24 | extract:0 ... := ... | exprs.go:33:5:33:24 | extract:1 ... := ... |
| exprs.go:33:5:33:24 | extract:1 ... := ... | exprs.go:33:5:33:24 | After ... := ... |
| exprs.go:33:14:33:16 | After arg | exprs.go:33:14:33:24 | type assertion |
| exprs.go:33:14:33:16 | Before arg | exprs.go:33:14:33:16 | arg |
| exprs.go:33:14:33:16 | arg | exprs.go:33:14:33:16 | After arg |
@@ -1238,10 +1236,8 @@
| exprs.go:41:6:41:12 | zero-init:0 value declaration specifier | exprs.go:41:6:41:12 | After value declaration specifier |
| exprs.go:42:2:42:20 | ... = ... | exprs.go:42:10:42:20 | Before type assertion |
| exprs.go:42:2:42:20 | After ... = ... | exprs.go:43:2:45:2 | if statement |
| exprs.go:42:2:42:20 | assign:0 ... = ... | exprs.go:42:2:42:20 | extract:1 ... = ... |
| exprs.go:42:2:42:20 | assign:1 ... = ... | exprs.go:42:2:42:20 | After ... = ... |
| exprs.go:42:2:42:20 | extract:0 ... = ... | exprs.go:42:2:42:20 | assign:0 ... = ... |
| exprs.go:42:2:42:20 | extract:1 ... = ... | exprs.go:42:2:42:20 | assign:1 ... = ... |
| exprs.go:42:2:42:20 | extract:0 ... = ... | exprs.go:42:2:42:20 | extract:1 ... = ... |
| exprs.go:42:2:42:20 | extract:1 ... = ... | exprs.go:42:2:42:20 | After ... = ... |
| exprs.go:42:10:42:12 | After arg | exprs.go:42:10:42:20 | type assertion |
| exprs.go:42:10:42:12 | Before arg | exprs.go:42:10:42:12 | arg |
| exprs.go:42:10:42:12 | arg | exprs.go:42:10:42:12 | After arg |
@@ -1515,10 +1511,8 @@
| exprs.go:81:35:87:1 | param-init:0 block statement | exprs.go:82:2:82:16 | ... := ... |
| exprs.go:82:2:82:16 | ... := ... | exprs.go:82:13:82:16 | Before <-... |
| exprs.go:82:2:82:16 | After ... := ... | exprs.go:83:2:85:2 | if statement |
| exprs.go:82:2:82:16 | assign:0 ... := ... | exprs.go:82:2:82:16 | extract:1 ... := ... |
| exprs.go:82:2:82:16 | assign:1 ... := ... | exprs.go:82:2:82:16 | After ... := ... |
| exprs.go:82:2:82:16 | extract:0 ... := ... | exprs.go:82:2:82:16 | assign:0 ... := ... |
| exprs.go:82:2:82:16 | extract:1 ... := ... | exprs.go:82:2:82:16 | assign:1 ... := ... |
| exprs.go:82:2:82:16 | extract:0 ... := ... | exprs.go:82:2:82:16 | extract:1 ... := ... |
| exprs.go:82:2:82:16 | extract:1 ... := ... | exprs.go:82:2:82:16 | After ... := ... |
| exprs.go:82:13:82:16 | <-... | exprs.go:82:13:82:16 | After <-... |
| exprs.go:82:13:82:16 | After <-... | exprs.go:82:2:82:16 | extract:0 ... := ... |
| exprs.go:82:13:82:16 | Before <-... | exprs.go:82:15:82:16 | Before ch |
@@ -1770,10 +1764,8 @@
| generic.go:30:24:30:24 | c | generic.go:30:24:30:24 | After c |
| generic.go:31:2:31:53 | ... := ... | generic.go:31:10:31:53 | Before call to genericIdentity2 |
| generic.go:31:2:31:53 | After ... := ... | generic.go:32:2:32:31 | ... := ... |
| generic.go:31:2:31:53 | assign:0 ... := ... | generic.go:31:2:31:53 | extract:1 ... := ... |
| generic.go:31:2:31:53 | assign:1 ... := ... | generic.go:31:2:31:53 | After ... := ... |
| generic.go:31:2:31:53 | extract:0 ... := ... | generic.go:31:2:31:53 | assign:0 ... := ... |
| generic.go:31:2:31:53 | extract:1 ... := ... | generic.go:31:2:31:53 | assign:1 ... := ... |
| generic.go:31:2:31:53 | extract:0 ... := ... | generic.go:31:2:31:53 | extract:1 ... := ... |
| generic.go:31:2:31:53 | extract:1 ... := ... | generic.go:31:2:31:53 | After ... := ... |
| generic.go:31:10:31:25 | After genericIdentity2 | generic.go:31:10:31:41 | generic function instantiation expression |
| generic.go:31:10:31:25 | Before genericIdentity2 | generic.go:31:10:31:25 | genericIdentity2 |
| generic.go:31:10:31:25 | genericIdentity2 | generic.go:31:10:31:25 | After genericIdentity2 |
@@ -1792,10 +1784,8 @@
| generic.go:31:46:31:52 | Before "hello" | generic.go:31:46:31:52 | "hello" |
| generic.go:32:2:32:31 | ... := ... | generic.go:32:10:32:31 | Before call to genericIdentity2 |
| generic.go:32:2:32:31 | After ... := ... | generic.go:33:2:33:6 | ... = ... |
| generic.go:32:2:32:31 | assign:0 ... := ... | generic.go:32:2:32:31 | extract:1 ... := ... |
| generic.go:32:2:32:31 | assign:1 ... := ... | generic.go:32:2:32:31 | After ... := ... |
| generic.go:32:2:32:31 | extract:0 ... := ... | generic.go:32:2:32:31 | assign:0 ... := ... |
| generic.go:32:2:32:31 | extract:1 ... := ... | generic.go:32:2:32:31 | assign:1 ... := ... |
| generic.go:32:2:32:31 | extract:0 ... := ... | generic.go:32:2:32:31 | extract:1 ... := ... |
| generic.go:32:2:32:31 | extract:1 ... := ... | generic.go:32:2:32:31 | After ... := ... |
| generic.go:32:10:32:25 | After genericIdentity2 | generic.go:32:27:32:27 | Before e |
| generic.go:32:10:32:25 | Before genericIdentity2 | generic.go:32:10:32:25 | genericIdentity2 |
| generic.go:32:10:32:25 | genericIdentity2 | generic.go:32:10:32:25 | After genericIdentity2 |
@@ -2587,9 +2577,8 @@
| stmts2.go:9:19:13:1 | block statement | stmts2.go:10:2:10:14 | ... := ... |
| stmts2.go:10:2:10:14 | ... := ... | stmts2.go:10:10:10:14 | Before call to gen |
| stmts2.go:10:2:10:14 | After ... := ... | stmts2.go:11:2:11:17 | declaration statement |
| stmts2.go:10:2:10:14 | assign:1 ... := ... | stmts2.go:10:2:10:14 | After ... := ... |
| stmts2.go:10:2:10:14 | extract:0 ... := ... | stmts2.go:10:2:10:14 | extract:1 ... := ... |
| stmts2.go:10:2:10:14 | extract:1 ... := ... | stmts2.go:10:2:10:14 | assign:1 ... := ... |
| stmts2.go:10:2:10:14 | extract:1 ... := ... | stmts2.go:10:2:10:14 | After ... := ... |
| stmts2.go:10:10:10:12 | After gen | stmts2.go:10:10:10:14 | call to gen |
| stmts2.go:10:10:10:12 | Before gen | stmts2.go:10:10:10:12 | gen |
| stmts2.go:10:10:10:12 | gen | stmts2.go:10:10:10:12 | After gen |
@@ -2602,9 +2591,8 @@
| stmts2.go:11:2:11:17 | declaration statement | stmts2.go:11:2:11:17 | variable declaration |
| stmts2.go:11:2:11:17 | variable declaration | stmts2.go:11:6:11:17 | value declaration specifier |
| stmts2.go:11:6:11:17 | After value declaration specifier | stmts2.go:11:2:11:17 | After variable declaration |
| stmts2.go:11:6:11:17 | assign:1 value declaration specifier | stmts2.go:11:6:11:17 | After value declaration specifier |
| stmts2.go:11:6:11:17 | extract:0 value declaration specifier | stmts2.go:11:6:11:17 | extract:1 value declaration specifier |
| stmts2.go:11:6:11:17 | extract:1 value declaration specifier | stmts2.go:11:6:11:17 | assign:1 value declaration specifier |
| stmts2.go:11:6:11:17 | extract:1 value declaration specifier | stmts2.go:11:6:11:17 | After value declaration specifier |
| stmts2.go:11:6:11:17 | value declaration specifier | stmts2.go:11:13:11:17 | Before call to gen |
| stmts2.go:11:13:11:15 | After gen | stmts2.go:11:13:11:17 | call to gen |
| stmts2.go:11:13:11:15 | Before gen | stmts2.go:11:13:11:15 | gen |
@@ -2649,8 +2637,7 @@
| stmts2.go:18:7:18:7 | After x | stmts2.go:18:10:18:10 | Before _ |
| stmts2.go:18:7:18:7 | Before x | stmts2.go:18:7:18:7 | x |
| stmts2.go:18:7:18:7 | x | stmts2.go:18:7:18:7 | After x |
| stmts2.go:18:7:18:18 | assign:0 ... := ... | stmts2.go:18:7:18:18 | extract:1 ... := ... |
| stmts2.go:18:7:18:18 | extract:0 ... := ... | stmts2.go:18:7:18:18 | assign:0 ... := ... |
| stmts2.go:18:7:18:18 | extract:0 ... := ... | stmts2.go:18:7:18:18 | extract:1 ... := ... |
| stmts2.go:18:7:18:18 | extract:1 ... := ... | stmts2.go:19:3:19:10 | Before return statement |
| stmts2.go:18:10:18:10 | After _ | stmts2.go:18:7:18:18 | extract:0 ... := ... |
| stmts2.go:18:10:18:10 | Before _ | stmts2.go:18:10:18:10 | _ |
@@ -2668,9 +2655,8 @@
| stmts2.go:20:7:20:7 | After _ | stmts2.go:20:10:20:10 | Before y |
| stmts2.go:20:7:20:7 | Before _ | stmts2.go:20:7:20:7 | _ |
| stmts2.go:20:7:20:7 | _ | stmts2.go:20:7:20:7 | After _ |
| stmts2.go:20:7:20:18 | assign:1 ... := ... | stmts2.go:21:3:23:3 | if statement |
| stmts2.go:20:7:20:18 | extract:0 ... := ... | stmts2.go:20:7:20:18 | extract:1 ... := ... |
| stmts2.go:20:7:20:18 | extract:1 ... := ... | stmts2.go:20:7:20:18 | assign:1 ... := ... |
| stmts2.go:20:7:20:18 | extract:1 ... := ... | stmts2.go:21:3:23:3 | if statement |
| stmts2.go:20:10:20:10 | After y | stmts2.go:20:7:20:18 | extract:0 ... := ... |
| stmts2.go:20:10:20:10 | Before y | stmts2.go:20:10:20:10 | y |
| stmts2.go:20:10:20:10 | y | stmts2.go:20:10:20:10 | After y |
@@ -2720,8 +2706,7 @@
| stmts2.go:30:20:34:1 | block statement | stmts2.go:31:2:31:14 | ... := ... |
| stmts2.go:31:2:31:14 | ... := ... | stmts2.go:31:10:31:14 | Before call to gen |
| stmts2.go:31:2:31:14 | After ... := ... | stmts2.go:32:2:32:17 | declaration statement |
| stmts2.go:31:2:31:14 | assign:0 ... := ... | stmts2.go:31:2:31:14 | extract:1 ... := ... |
| stmts2.go:31:2:31:14 | extract:0 ... := ... | stmts2.go:31:2:31:14 | assign:0 ... := ... |
| stmts2.go:31:2:31:14 | extract:0 ... := ... | stmts2.go:31:2:31:14 | extract:1 ... := ... |
| stmts2.go:31:2:31:14 | extract:1 ... := ... | stmts2.go:31:2:31:14 | After ... := ... |
| stmts2.go:31:10:31:12 | After gen | stmts2.go:31:10:31:14 | call to gen |
| stmts2.go:31:10:31:12 | Before gen | stmts2.go:31:10:31:12 | gen |
@@ -2735,8 +2720,7 @@
| stmts2.go:32:2:32:17 | declaration statement | stmts2.go:32:2:32:17 | variable declaration |
| stmts2.go:32:2:32:17 | variable declaration | stmts2.go:32:6:32:17 | value declaration specifier |
| stmts2.go:32:6:32:17 | After value declaration specifier | stmts2.go:32:2:32:17 | After variable declaration |
| stmts2.go:32:6:32:17 | assign:0 value declaration specifier | stmts2.go:32:6:32:17 | extract:1 value declaration specifier |
| stmts2.go:32:6:32:17 | extract:0 value declaration specifier | stmts2.go:32:6:32:17 | assign:0 value declaration specifier |
| stmts2.go:32:6:32:17 | extract:0 value declaration specifier | stmts2.go:32:6:32:17 | extract:1 value declaration specifier |
| stmts2.go:32:6:32:17 | extract:1 value declaration specifier | stmts2.go:32:6:32:17 | After value declaration specifier |
| stmts2.go:32:6:32:17 | value declaration specifier | stmts2.go:32:13:32:17 | Before call to gen |
| stmts2.go:32:13:32:15 | After gen | stmts2.go:32:13:32:17 | call to gen |
@@ -3341,10 +3325,8 @@
| stmts.go:53:7:53:10 | Before index expression | stmts.go:53:7:53:7 | Before a |
| stmts.go:53:7:53:10 | index expression | stmts.go:46:1:62:1 | Exceptional Exit |
| stmts.go:53:7:53:10 | index expression | stmts.go:53:7:53:10 | After index expression |
| stmts.go:53:7:53:21 | assign:0 ... = ... | stmts.go:53:7:53:21 | extract:1 ... = ... |
| stmts.go:53:7:53:21 | assign:1 ... = ... | stmts.go:54:3:54:16 | expression statement |
| stmts.go:53:7:53:21 | extract:0 ... = ... | stmts.go:53:7:53:21 | assign:0 ... = ... |
| stmts.go:53:7:53:21 | extract:1 ... = ... | stmts.go:53:7:53:21 | assign:1 ... = ... |
| stmts.go:53:7:53:21 | extract:0 ... = ... | stmts.go:53:7:53:21 | extract:1 ... = ... |
| stmts.go:53:7:53:21 | extract:1 ... = ... | stmts.go:54:3:54:16 | expression statement |
| stmts.go:53:9:53:9 | 0 | stmts.go:53:9:53:9 | After 0 |
| stmts.go:53:9:53:9 | After 0 | stmts.go:53:7:53:10 | index expression |
| stmts.go:53:9:53:9 | Before 0 | stmts.go:53:9:53:9 | 0 |
@@ -3873,8 +3855,7 @@
| stmts.go:146:2:151:2 | After range statement | stmts.go:153:2:155:2 | range statement |
| stmts.go:146:2:151:2 | [LoopHeader] range statement | stmts.go:146:2:151:2 | After range statement |
| stmts.go:146:2:151:2 | [LoopHeader] range statement | stmts.go:146:2:151:2 | range element |
| stmts.go:146:2:151:2 | assign:0 range element | stmts.go:146:2:151:2 | After range element |
| stmts.go:146:2:151:2 | extract:0 range element | stmts.go:146:2:151:2 | assign:0 range element |
| stmts.go:146:2:151:2 | extract:0 range element | stmts.go:146:2:151:2 | After range element |
| stmts.go:146:2:151:2 | next range element | stmts.go:146:2:151:2 | extract:0 range element |
| stmts.go:146:2:151:2 | range element | stmts.go:146:2:151:2 | next range element |
| stmts.go:146:2:151:2 | range statement | stmts.go:146:17:146:18 | Before xs |
@@ -3917,10 +3898,8 @@
| stmts.go:153:2:155:2 | After range statement | stmts.go:157:2:158:2 | range statement |
| stmts.go:153:2:155:2 | [LoopHeader] range statement | stmts.go:153:2:155:2 | After range statement |
| stmts.go:153:2:155:2 | [LoopHeader] range statement | stmts.go:153:2:155:2 | range element |
| stmts.go:153:2:155:2 | assign:0 range element | stmts.go:153:2:155:2 | extract:1 range element |
| stmts.go:153:2:155:2 | assign:1 range element | stmts.go:153:2:155:2 | After range element |
| stmts.go:153:2:155:2 | extract:0 range element | stmts.go:153:2:155:2 | assign:0 range element |
| stmts.go:153:2:155:2 | extract:1 range element | stmts.go:153:2:155:2 | assign:1 range element |
| stmts.go:153:2:155:2 | extract:0 range element | stmts.go:153:2:155:2 | extract:1 range element |
| stmts.go:153:2:155:2 | extract:1 range element | stmts.go:153:2:155:2 | After range element |
| stmts.go:153:2:155:2 | next range element | stmts.go:153:2:155:2 | extract:0 range element |
| stmts.go:153:2:155:2 | range element | stmts.go:153:2:155:2 | next range element |
| stmts.go:153:2:155:2 | range statement | stmts.go:153:20:153:21 | Before xs |