Python: Add support for late *args arguments

This commit is contained in:
Rasmus Wriedt Larsen
2022-09-20 09:12:00 +02:00
parent 035d083515
commit 98a849405f
4 changed files with 78 additions and 2 deletions

View File

@@ -50,6 +50,7 @@ newtype TParameterPosition =
pos = any(Parameter p).getPosition() + 1
} or
TSynthStarArgsElementParameterPosition(int pos) { exists(TStarArgsParameterPosition(pos)) } or
TSynthLateStarArgsParameterPosition(int pos) { exists(TStarArgsParameterPosition(pos)) } or
TDictSplatParameterPosition()
/** A parameter position. */
@@ -75,6 +76,14 @@ class ParameterPosition extends TParameterPosition {
this = TSynthStarArgsElementParameterPosition(index)
}
/**
* Holds if this position represents a synthetic `*args` parameter after the real
* `*args` parameter. The real `*args` parameter is at the 0-based index `index`.
*/
predicate isSynthLateStarArgsParameterPosition(int index) {
this = TSynthLateStarArgsParameterPosition(index)
}
/** Holds if this position represents a `**kwargs` parameter. */
predicate isDictSplat() { this = TDictSplatParameterPosition() }
@@ -93,6 +102,11 @@ class ParameterPosition extends TParameterPosition {
result = "synthetic *args element at (or after) " + index
)
or
exists(int index |
this.isSynthLateStarArgsParameterPosition(index) and
result = "synthetic late *args after " + index
)
or
this.isDictSplat() and result = "**"
}
}
@@ -151,6 +165,10 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
ppos.isSynthStarArgsElement(paramIndex) and apos.isPositional(argIndex)
)
or
exists(int realStarArgsIndex, int argIndex | argIndex > realStarArgsIndex |
ppos.isSynthLateStarArgsParameterPosition(realStarArgsIndex) and apos.isStarArgs(argIndex)
)
or
ppos.isDictSplat() and apos.isDictSplat()
}
@@ -244,6 +262,9 @@ abstract class DataFlowFunction extends DataFlowCallable, TFunction {
or
ppos.isSynthStarArgsElement(index) and
result = TSynthStarArgsElementParameterNode(this)
or
ppos.isSynthLateStarArgsParameterPosition(index) and
result = TSynthLateStarArgsParameterNode(this)
)
|
// a `*args` parameter comes after the last positional parameter. We need to take

View File

@@ -129,6 +129,49 @@ predicate synthStarArgsElementParameterNodeStoreStep(
)
}
/**
* A synthetic node to capture a `*args` argument that is passed to a `*args`
* parameter, but "too late" in the argument list, so we cannot just do a 1-1 mapping
* without messing up the indexes; instead we make a list/tuple/set read step to
* `SynthStarArgsElementParameterNode`.
*
* Example. The `*args` arguments starts at index 1, while the `*args` parameter accepts
* arguments starting at index 0.
*
* ```py
* def func(*args): ...
* func(1, *args)
*/
class SynthLateStarArgsParameterNode extends ParameterNodeImpl, TSynthLateStarArgsParameterNode {
DataFlowCallable callable;
SynthLateStarArgsParameterNode() { this = TSynthLateStarArgsParameterNode(callable) }
override string toString() { result = "SynthLateStarArgsParameterNode" }
override Scope getScope() { result = callable.getScope() }
override Location getLocation() { result = callable.getLocation() }
override Parameter getParameter() { none() }
}
predicate synthLateStarArgsParameterNodeReadStep(
SynthLateStarArgsParameterNode nodeFrom, Content c, ParameterNode nodeTo
) {
(
c instanceof ListElementContent
or
c instanceof TupleElementContent
or
c instanceof SetElementContent
) and
exists(DataFlowCallable callable |
nodeFrom = TSynthLateStarArgsParameterNode(callable) and
nodeTo = TSynthStarArgsElementParameterNode(callable)
)
}
// =============================================================================
// **kwargs (DictSplat) related
// =============================================================================
@@ -764,6 +807,8 @@ predicate readStep(Node nodeFrom, Content c, Node nodeTo) {
or
FlowSummaryImpl::Private::Steps::summaryReadStep(nodeFrom, c, nodeTo)
or
synthLateStarArgsParameterNodeReadStep(nodeFrom, c, nodeTo)
or
synthDictSplatParameterNodeReadStep(nodeFrom, c, nodeTo)
}
@@ -925,6 +970,8 @@ predicate nodeIsHidden(Node n) {
or
n instanceof SynthStarArgsElementParameterNode
or
n instanceof SynthLateStarArgsParameterNode
or
n instanceof SynthDictSplatArgumentNode
or
n instanceof SynthDictSplatParameterNode

View File

@@ -117,6 +117,14 @@ newtype TNode =
TSynthStarArgsElementParameterNode(DataFlowCallable callable) {
exists(ParameterPosition ppos | ppos.isStarArgs(_) | exists(callable.getParameter(ppos)))
} or
/**
* A synthetic node to capture a `*args` argument that is passed to a `*args`
* parameter, but "too late" in the argument list, so we cannot just do a 1-1 mapping
* without messing up the indexes.
*/
TSynthLateStarArgsParameterNode(DataFlowCallable callable) {
exists(ParameterPosition ppos | ppos.isStarArgs(_) | exists(callable.getParameter(ppos)))
} or
/** A synthetic node to capture keyword arguments that are passed to a `**kwargs` parameter. */
TSynthDictSplatArgumentNode(CallNode call) { exists(call.getArgByName(_)) } or
/** A synthetic node to allow flow to keyword parameters from a `**kwargs` argument. */

View File

@@ -211,8 +211,8 @@ def starargs_only(*args):
def test_only_starargs():
starargs_only(arg1, arg2, "safe") # $ arg1 arg2 SPURIOUS: bad2,bad3="arg1" bad1,bad3="arg2"
args = (arg2, "safe")
starargs_only(arg1, *args) # $ arg1 SPURIOUS: bad2,bad3="arg1" MISSING: arg2
args = (arg2, "safe") # $ arg2 func=starargs_only SPURIOUS: bad1,bad3="arg2"
starargs_only(arg1, *args) # $ arg1 SPURIOUS: bad2,bad3="arg1"
args = (arg1, arg2, "safe") # $ arg1 arg2 func=starargs_only
starargs_only(*args)