Ruby: support anonymous (hash)splat parameters/arguments

This commit is contained in:
Arthur Baars
2023-01-10 12:52:45 +01:00
parent 4d3e2bb814
commit c4ec674057
6 changed files with 62 additions and 22 deletions

View File

@@ -181,7 +181,10 @@ class HashSplatParameter extends NamedParameter, THashSplatParameter {
final override string getAPrimaryQlClass() { result = "HashSplatParameter" }
final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
final override LocalVariable getVariable() {
result = TLocalVariableReal(_, _, g.getName()) or
result = TLocalVariableSynth(this, 0)
}
final override string toString() {
result = "**" + this.getName()
@@ -307,7 +310,10 @@ class SplatParameter extends NamedParameter, TSplatParameter {
final override string getAPrimaryQlClass() { result = "SplatParameter" }
final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
final override LocalVariable getVariable() {
result = TLocalVariableReal(_, _, g.getName()) or
result = TLocalVariableSynth(this, 0)
}
final override string toString() {
result = "*" + this.getName()

View File

@@ -36,7 +36,7 @@ class LocalVariable extends Variable, TLocalVariable {
/** Gets the access where this local variable is first introduced. */
VariableAccess getDefiningAccess() {
result = this.(LocalVariableReal).getDefiningAccessImpl() or
synthChild(any(BlockParameter p | this = p.getVariable()), 0, result)
synthChild(any(NamedParameter p | this = p.getVariable()), 0, result)
}
/**
@@ -120,7 +120,7 @@ class VariableAccess extends Expr instanceof VariableAccessImpl {
or
this = any(HashPattern p).getValue(_)
or
synthChild(any(BlockParameter p), 0, this)
synthChild(any(NamedParameter p), 0, this)
}
}

View File

@@ -42,7 +42,10 @@ class SplatExprReal extends UnaryOperationImpl, TSplatExprReal {
final override string getOperatorImpl() { result = "*" }
final override Expr getOperandImpl() { toGenerated(result) = g.getChild() }
final override Expr getOperandImpl() {
toGenerated(result) = g.getChild() or
synthChild(this, 0, result)
}
}
class SplatExprSynth extends UnaryOperationImpl, TSplatExprSynth {
@@ -56,7 +59,10 @@ class HashSplatExprImpl extends UnaryOperationImpl, THashSplatExpr {
HashSplatExprImpl() { this = THashSplatExpr(g) }
final override Expr getOperandImpl() { toGenerated(result) = g.getChild() }
final override Expr getOperandImpl() {
toGenerated(result) = g.getChild() or
synthChild(this, 0, result)
}
final override string getOperatorImpl() { result = "**" }
}

View File

@@ -1317,46 +1317,62 @@ private module ImplicitHashValueSynthesis {
/**
* ```rb
* def foo(&)
* bar(&)
* def foo(*, **, &)
* bar(*, **, &)
* end
* ```
* desugars to,
* ```rb
* def foo(&__synth_0)
* bar(&__synth_0)
* def foo(*__synth_0, **__synth_1, &__synth_2)
* bar(*__synth_0, **__synth_1, &__synth_2)
* end
* ```
*/
private module AnonymousBlockParameterSynth {
private BlockParameter anonymousBlockParameter() {
private module AnonymousParameterSynth {
private class AnonymousParameter = TBlockParameter or THashSplatParameter or TSplatParameter;
private class AnonymousArgument = TBlockArgument or THashSplatExpr or TSplatExpr;
private AnonymousParameter anonymousParameter() {
exists(Ruby::BlockParameter p | not exists(p.getName()) and toGenerated(result) = p)
or
exists(Ruby::SplatParameter p | not exists(p.getName()) and toGenerated(result) = p)
or
exists(Ruby::HashSplatParameter p | not exists(p.getName()) and toGenerated(result) = p)
}
private BlockArgument anonymousBlockArgument() {
private AnonymousArgument anonymousArgument() {
exists(Ruby::BlockArgument p | not exists(p.getChild()) and toGenerated(result) = p)
or
exists(Ruby::SplatArgument p | not exists(p.getChild()) and toGenerated(result) = p)
or
exists(Ruby::HashSplatArgument p | not exists(p.getChild()) and toGenerated(result) = p)
}
private class AnonymousBlockParameterSynthesis extends Synthesis {
private class AnonymousParameterSynthesis extends Synthesis {
final override predicate child(AstNode parent, int i, Child child) {
i = 0 and
parent = anonymousBlockParameter() and
parent = anonymousParameter() and
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(parent, 0)))
}
final override predicate localVariable(AstNode n, int i) {
n = anonymousBlockParameter() and i = 0
}
final override predicate localVariable(AstNode n, int i) { n = anonymousParameter() and i = 0 }
}
private class AnonymousBlockArgumentSynthesis extends Synthesis {
private class AnonymousArgumentSynthesis extends Synthesis {
final override predicate child(AstNode parent, int i, Child child) {
i = 0 and
parent = anonymousBlockArgument() and
exists(BlockParameter param |
param = anonymousBlockParameter() and
parent = anonymousArgument() and
exists(AnonymousParameter param |
param = anonymousParameter() and
scopeOf(toGenerated(parent)).getEnclosingMethod() = scopeOf(toGenerated(param)) and
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
|
param instanceof TBlockParameter and parent instanceof TBlockArgument
or
param instanceof TSplatParameter and parent instanceof TSplatExpr
or
param instanceof THashSplatParameter and parent instanceof THashSplatExpr
)
}
}

View File

@@ -269,10 +269,12 @@ foo(&)
# splat argument
foo(*bar)
foo(*X::bar)
foo(*)
# hash-splat argument
foo(**bar)
foo(**X::bar)
foo(**)
# the value in a keyword argument
foo(blah: bar)

View File

@@ -84,3 +84,13 @@ def anonymous_block_parameter(array, &)
end
run_block { |x; y, z | puts x }
# Anonymous splat parameter
def anonymous_splat_parameter(array, *)
array.concat(*)
end
# Anonymous hash splat parameter
def anonymous_hash_splat_parameter(hash, **)
hash.merge(**)
end